# Beyz Scripts API

Referencia publica unica da API v1. Atualizada em 2026-10-09.
As extensoes disponiveis variam conforme a versao instalada do Beyz.
Detecte cada capability com `beyz.supports` antes de utiliza-la.

## Transformacao de Texto Nativa

Detecte suporte com `beyz.supports("effects.textTransform")`. O efeito altera
unidades de uma unica camada de texto antes da rasterizacao, sem criar uma camada
por letra e sem executar JavaScript durante o play. Permissoes: `project.read`
para leitura e `effects.modify` (ou a permissao existente `layers.modify`) para
escrita. Referencias, bloqueios, IDs e limites sao validados atomicamente.

### Criacao e Configuracao

```javascript
const effect = beyz.effects.add(textLayerId, "text_transform");
const selector = beyz.effects.addTextSelector(textLayerId, effect, {
  unit: "Characters", profile: "Smooth", order: "Reading"
});
beyz.effects.configureTextTransform(textLayerId, effect, {
  activeProperties: ["position_y", "opacity"],
  anchorGroup: "Unit", anchorAlignment: "Center", gradientSpace: "Source"
});
beyz.effects.setFloatParam(textLayerId, effect, "position_y", 40);
beyz.effects.setFloatParam(textLayerId, effect, "opacity", 0);
beyz.effects.setFloatKeyframe(textLayerId, effect,
  "selector." + selector + ".offset", 0, 0);
beyz.effects.setFloatKeyframe(textLayerId, effect,
  "selector." + selector + ".offset", 1000, 100);
```

`add` tambem aceita `motionlayer.text_transform` e retorna o ID da instancia.
A instancia publica comeca neutra, sem propriedades ativas ou seletores.
Sem seletores, a influencia cobre todas as unidades; um seletor novo cobre
0..100 por padrao. O app insere texto antes do primeiro efeito raster.
Nao e permitido mover o efeito depois de um efeito raster.

Metodos adicionais:

| Metodo | Contrato |
| --- | --- |
| `addTextSelector(layer, effect, options?, index?)` | Retorna ID; indice zero-based opcional. `options.id` pode ser fornecido. |
| `updateTextSelector(layer, effect, selector)` | Substitui a configuracao estatica completa, preservando o ID. Para mudar `%`/indice, use conversao. |
| `removeTextSelector(layer, effect, id)` | Remove seus canais; recusa se algum controller ainda os referencia. |
| `duplicateTextSelector(layer, effect, id, newId?)` | Retorna novo ID; clona canais/curvas e remapeia seus paths. |
| `reorderTextSelector(layer, effect, id, index)` | Reordena a combinacao, sem trocar a identidade dos canais. |
| `convertTextSelectorRange(layer, effect, id, rangeUnit, unitCount)` | Converte defaults e todos os keyframes de start/end/offset usando uma contagem positiva explicita. |
| `activateTextProperty(layer, effect, property, active)` | Ativa/desativa sem apagar valores ou keyframes. |
| `configureTextTransform(layer, effect, options)` | Merge de campos opcionais; preserva seletores. `activeProperties` substitui o conjunto completo. |

`effects.list(layer)` inclui `textTransform` com seletores, propriedades ativas,
grupos, alinhamento, espaco do gradiente e versao de segmentacao.
`effects.properties` mostra os canais ativos, influencia e seletores. Os canais
preservados continuam presentes no snapshot; desativar nao e apagar.
O snapshot e imutavel: list/get nao simulam operacoes ainda nao aplicadas no lote.

### Propriedades Disponiveis

| ID | Unidade / default | Significado |
| --- | --- | --- |
| `position_x`, `position_y` | px / 0 | Deslocamento local das unidades selecionadas. |
| `scale_x`, `scale_y` | % / 100 | Escala por eixo; zero remove a cobertura. |
| `rotation_z` | graus / 0 | Rotacao no plano do texto. |
| `skew`, `skew_axis` | graus / 0 | Inclinacao (-89..89) e eixo da inclinacao. |
| `anchor_x`, `anchor_y` | px / 0 | Deslocamento da ancora de transformacao. |
| `opacity` | % / 100 | Opacidade multiplicativa (0..100). |
| `fill_color`, `stroke_color` | Color / branco | Cores alvo; use a API Color, inclusive keyframes. |
| `fill_mix`, `stroke_mix` | % / 0 | Mistura da cor alvo (0..100); ative e aumente para aplicar a cor. |
| `stroke_width` | px / 0 | Acrescimo de largura ao stroke (>=0); cor/mistura podem introduzir stroke. |
| `tracking`, `line_spacing` | px / 0 | Espacamento adicional entre clusters seguros e entre linhas; nao altera o shaping original. |
| `blur_x`, `blur_y` | px / 0 | Desfoque independente por unidade/eixo, antes de combinar os planos (0..2000). Requer `effects.textTransformUnitBlur`; indisponivel com extrusao habilitada. |
| `position_z`, `anchor_z` | px / 0 | Deslocamento/ancora espacial em camada 3D; requer `effects.textTransformSpatialPlanes`. |
| `scale_z` | % / 100 | Escala real da profundidade, inclusive zero/negativa, quando houver volume qualificado. |
| `rotation_x`, `rotation_y` | graus / 0 | Rotacao espacial independente; somente em camada 3D. |
| `influence` | % / 100 | Forca global (-200..200), sempre disponivel. |
| `delay_ms` | ms / 0 | Atraso por rank da unidade (0..100000). |
| `delay_origin_ms` | ms / 0 | Origem da sequencia no tempo local (0..10000000). |
| `delay_seed` | inteiro / 0 | Semente animavel para a ordem aleatoria do atraso. |

Usar `setFloatParam`, `setFloatKeyframe`, `setColorParam`, `setColorKeyframe`,
`removeKeyframe` e `moveKeyframe` existentes. Curvas usam as opcoes nativas
`interpolation: "linear" | "hold" | "ease"`, controles Bezier e resposta elastica.
Cores usam canais Color, nao numeros escalares. Um lote de script tem um undo.
Os paths de referencia sao `effects.<effectId>.<parameterId>`; controllers
numericos podem escrever nos parametros escalares suportados, nao em Color.

`anchorGroup`: `Unit`, `Word`, `Line`, `Block`.
`anchorAlignment`: `Center`, `Baseline`, `Custom`.
`gradientSpace`: `Source` (preserva o gradiente da fonte) ou `Unit`
(o gradiente acompanha a unidade). Em uma pilha, vale o dominio da ultima
instancia habilitada. Seletores nunca mudam o shaping contextual para separar
artificialmente letras de uma ligatura.

### Extensoes Espaciais

`effects.textTransformSpatialPlanes` e `effects.textTransformUnitBlur`
identificam os contratos espaciais. Detecte essas capabilities separadamente;
nao infira esses recursos do suporte basico.
`configureTextTransform` aceita `faceCamera: true | false`; o snapshot inclui
o mesmo campo. Em 3D, orienta os eixos da unidade para a camera, conservando
sua ancora world, escala, reflexao e shear. Em camada 2D, pedir `true` e erro.
Desativar 3D conserva canais XYZ salvos, mas deixa-os inativos no render 2D.

O snapshot `effect.textTransform.unitBlurSupported` informa se a camada pode
ativar `blur_x`/`blur_y`. E `false` quando a camada 3D possui extrusao habilitada,
mesmo em profundidade zero. Ativar blur nessa combinacao, ou habilitar extrusao
com blur ativo, e recusado atomicamente: nao apaga parametros/keys e nao
aplica parcialmente o lote. Desative a extrusao para usar blur por unidade.
Um efeito inteiro desabilitado pode conservar sua configuracao dormente.
`blur_x`/`blur_y` aceitam 0..2000px, tanto no valor base quanto nos keyframes.
Valores fora do intervalo fazem o lote falhar antes de mutacao/preview; nao sao
clampados. O raio efetivo depois de combinar influencias/instancias tambem tem
budget de 2000px por eixo e e recusado explicitamente acima disso.

```javascript
if (!beyz.supports("effects.textTransformSpatialPlanes")) {
  throw new Error("Este Beyz nao oferece transformacoes espaciais de texto");
}
// textLayerId deve referenciar uma camada de texto em modo 3D.
const spatial = beyz.effects.add(textLayerId, "text_transform");
beyz.effects.configureTextTransform(textLayerId, spatial, {
  activeProperties: ["rotation_y", "position_z"], faceCamera: false
});
beyz.effects.setFloatKeyframe(textLayerId, spatial, "rotation_y", 0, -80);
beyz.effects.setFloatKeyframe(textLayerId, spatial, "rotation_y", 1000, 0);
beyz.effects.setFloatParam(textLayerId, spatial, "position_z", -40);
```

Planos contextuais usam a fonte e o shaping do Android, com raster na densidade
projetada e profundidade real compartilhada. Extrusao independente requer
Android 12/API31+ e contornos da fonte real qualificados. Esse requisito nao
aumenta o Android minimo nem restringe o texto 2D/planar existente.

### Sequencias e Seletores Avancados

Extensao adicional: `beyz.supports("effects.textTransformSequences")`.
Nao presumir seu suporte apenas porque a extensao basica de texto existe.
Inclui sequencias e Motion Blur temporal quando a capability esta disponivel.

`configureTextTransform` tambem aceita `spacingAnchor: "Start" | "Center" | "End"`,
`delayUnit` com as mesmas unidades dos seletores e `delayOrder` com as mesmas ordens.
O atraso amostra as propriedades em `max(0, t_local - origin_ms - delay_ms * rank)`;
seletores/influencia continuam em `t_local`. Nao move keys nem cria relogio.
Palavras/linhas compartilham o rank. Ligaturas inseparaveis usam a media dos ranks
dos seus grafemas. Armazenamento em ms conserva o tempo ao alterar FPS.

Seletores agora aceitam `kind: "Range" | "Wave" | "Variation" | "Pattern"`.
Trocar o tipo preserva IDs e todos os canais, inclusive os temporariamente ocultos.

| Tipo / campos | Canais adicionais e comportamento |
| --- | --- |
| `Wave`, `waveShape` (`Sine`, `Triangle`) | `.amplitude` 100% (0..100), `.wavelength` 8 unidades (>=.01), `.frequency` 0 Hz, `.phase` 0 graus. Peso 0..1, aplicado segundo a ordem/combinacao; frequencia pode ser negativa. |
| `Variation` | `.minimum` 0%, `.maximum` 100% (-100..100), `.frequency` 0 Hz, `.phase` 0 graus, `.spatial_phase` 0 graus por unidade, `.correlation` 0% (0..100). Ruido deterministico suavizado entre amostras; correlacao 100% usa o componente comum. Peso assinado multiplica a mascara combinada, separado de interseccao/min/max. |
| `Pattern`, `patternMode` (`Every`, `First`, `Last`) | `.period` 2 (>=1), `.pattern_offset` 0; period/offset arredondados para unidades inteiras. First/Last selecionam o primeiro/ultimo rank da ordem escolhida. |
| Ordem aleatoria / variacao | `.seed` 0 e um deslocamento inteiro animavel sobre a semente-base estatica. Mantem integralmente a semente antiga de 32 bits; soma com wrap de 32 bits. |

Todos os IDs acima usam `selector.<id>.<canal>`. Seed/period/offset sao
discretizados; interpolacao Hold e util para saltos deliberados. Frequencia e
fase usam fase absoluta: `phase / 360 + t_local / 1000 * frequency`, sem integrar
o historico da velocidade. Anime phase para controle continuo de fase.
Variation respeita amount/inverted; combinacao nao se aplica ao multiplicador
assinado. Para excluir unidades, use um seletor de intervalo/padrao adicional.

```javascript
if (!beyz.supports("effects.textTransformSequences")) throw new Error("Atualize o Beyz");
const fx = beyz.effects.add(textLayerId, "text_transform");
const wave = beyz.effects.addTextSelector(textLayerId, fx, {
  kind: "Wave", unit: "CharactersWithoutSpaces", waveShape: "Sine"
});
beyz.effects.configureTextTransform(textLayerId, fx, {
  activeProperties: ["position_y", "tracking"], spacingAnchor: "Center"
});
beyz.effects.setFloatParam(textLayerId, fx, "position_y", 32);
beyz.effects.setFloatParam(textLayerId, fx, "tracking", 4);
beyz.effects.setFloatKeyframe(textLayerId, fx, "selector." + wave + ".phase", 0, 0);
beyz.effects.setFloatKeyframe(textLayerId, fx, "selector." + wave + ".phase", 1000, 360);
```

### Seletores

IDs usam apenas letras ASCII, numeros, `_` e `-`. As enums sao case-sensitive:

- `unit`: `Characters`, `CharactersWithoutSpaces`, `Words`, `Lines`.
- `rangeUnit`: `Percentage`, `Index`.
- `profile`: `Block`, `Rising`, `Falling`, `Triangle`, `Round`, `Smooth`.
- `order`: `Reading`, `Reverse`, `CenterOut`, `OutsideIn`, `Random`.
- `combination`: `Replace`, `Add`, `Subtract`, `Intersect`, `Minimum`, `Maximum`, `Multiply`.
- `inverted`, `wrap`, `enabled`: Boolean; `seed`: inteiro estatico.

Parametros animados: `selector.<id>.start` (0), `.end` (100), `.offset` (0),
`.amount` (100, 0..100%), `.softness` (0, 0..100%), `.ease_in` e `.ease_out`
(1, .01..100). Start/end/offset usam porcentagem ou indice; ease sao fatores
dimensionless, mostrados em porcentagem na UI (1 = 100%). Amount e influencia
controlam forca; offset desloca o intervalo sem alterar sua largura; wrap
permite dar a volta no dominio. Ordem Random usa seed deterministica.
Perfis controlam a distribuicao da influencia dentro da faixa; combinacoes
sao avaliadas na ordem dos seletores e nao dependem do historico de scrub.

`layer.textUnitCounts` fornece contagens ICU 76.1/Unicode 16 do **texto-base**
no app: chaves `Characters`, `CharactersWithoutSpaces`, `Words`, `Lines`.
Caracteres sao grafemas, nao `string.length`. Linhas sao LF explicitos.
Para converter intervalo, informe a contagem correspondente em `unitCount`.
Texto procedural pode diferir do texto-base: nesse caso informe a contagem
do conteudo que servira de referencia. A conversao nao grava uma contagem
fixa no seletor e nao muda o modo de segmentacao do projeto.

Exemplos devem ser empacotados como `.beyzscript`, com manifest e main.js.
Ele nao vem instalado como script padrao do app.

### Limites Atuais

Planos espaciais, XYZ, faceCamera e blur por unidade dependem das capabilities
espaciais. Quebra opcional de ligaturas permanece recusada.
Blur por unidade e indisponivel com extrusao habilitada; use
`unitBlurSupported` no snapshot. Extrusao por glifo exige API31+ e contornos
vetoriais qualificaveis. Nunca substituir o volume por um quad.
Propriedades/combinações sem suporte sao recusadas;
nao sao atalhos escondidos no script.
Fontes/fallbacks que nao passam a qualificacao tipografica sao recusados
explicitamente. O overlay e somente preview e nunca faz parte da exportacao.
Caches sao limitados; nao ha promessa universal de FPS ou de igualdade com
outros editores.
Fontes empacotadas nao sao substituidas
ao importar: familias portateis requerem API29+ e a identificacao automatica completa
dos fallbacks requer API31+. GIF e video usam codecs com perda; PNG/sequencia
preservam pixels e alpha. Esses requisitos nao mudam o Android minimo do Beyz.
Motion Blur amostra as deformacoes internas do texto no ponto correto da pilha,
incluindo prefixo raster, grupo/remap, captura/ajuste e texto planar 3D.
Usa os controles publicos existentes de exposicao/amostragem, sem helper privado.
Pilhas temporais que excedem 256 cenas por frame sao recusadas explicitamente;
alta amostragem tem custo real. Captura temporal exata de video nao foi ampliada
pela API de scripts.
Efeito schema 2: leitores anteriores devem rejeita-lo. O mapper atual migra
explicitamente schema 1 com defaults neutros, conservando canais e sementes.

## Contadores de texto

Detecte suporte com `beyz.supports("procedural.text")`. A extensao aceita
`textOutputs` em `procedural.put`; list/get e snapshots preservam esses campos.
Permissao de escrita: `layers.modify`; list/get exigem `project.read`, como os
programas numericos. Referencias, bloqueios e escritor unico sao validados.

O contrato adiciona `textOutputs: [{layerId, nodeId, format}]` ao
programa. `nodeId` deve produzir Number e `layerId` deve apontar para texto;
apenas um escritor pode substituir o conteudo de cada camada. O texto-base e
seu estilo permanecem preservados. Nao ha leitura textual Base/Effective,
concatenacao de grafos ou modo Add nesta extensao inicial.

```javascript
if (!beyz.supports('procedural.text')) throw new Error('Atualize o Beyz.');
beyz.procedural.put({
  id: 'my-counter', hostLayerId: textId,
  parameters: [{id:'value', type:'Number', channels:[{defaultValue:0,
    keyframes:[{timeMs:0,value:0},{timeMs:1000,value:100}]}]}],
  nodes:[{id:'value',operation:'Parameter',type:'Number',parameterId:'value'}],
  outputs:[], textOutputs:[{layerId:textId,nodeId:'value',format:{decimals:2}}]
});
```

No exemplo acima, timeMs e o tempo local do host. `bakeCurrent` grava o conteudo
atual e remove o programa com undo; remove/setEnabled(false) restaura o base.
Saidas numericas e textuais compartilham o limite agregado de 1024 por composicao.
O exemplo de contador mostra apenas a referencia
na configuracao inicial; valor/keyframes e formato ficam na ferramenta aplicada.
Aplicar formato preserva os canais animados do valor. O valor do exemplo possui
limites -1.000.000..1.000.000, que outros scripts podem declarar diferentemente.

O contador e avaliado nativamente pelo tempo da timeline; nao execute JavaScript
por frame nem crie uma camada para cada numero. Valores constantes reutilizam
a ultima formatacao. Quando o numero muda mas o texto arredondado permanece
igual, o payload/layout pode ser reutilizado. Numeros novos precisam de layout
e, em texto extrudado, de geometria nova: muitos contadores 3D com efeitos nao
possuem garantia de 60/120 fps. Os caches sao limitados, nao um historico infinito
de valores.

`format` tem defaults explicitos e persistidos:

| Campo | Default / contrato |
| --- | --- |
| kind | Number; alternativas Percent, Currency, Duration |
| decimals | 0; intervalo 0..10 |
| roundingMode | HalfUp (metade para longe de zero), Down (para zero), Up (longe de zero) |
| groupThousands | false |
| groupingSeparator / decimalSeparator | `,` / `.`; diferentes, 1..4 caracteres, sem digitos |
| minIntegerDigits | 1; intervalo 1..12 |
| prefix / suffix | vazios; ate 64 caracteres cada |
| currencySymbol | `$`; ate 16 caracteres, obrigatorio em Currency |
| durationInputUnit | Seconds; alternativa Milliseconds |

Percent recebe fracao: 0.25 produz 25%. Currency nao converte moedas. Duration
produz H:MM:SS com fracao opcional, horas sem truncamento e sinal para duracoes
negativas. Arredondamento que resulta em zero elimina o sinal negativo.
NaN/infinito sao recusados; saida limitada a 256 unidades UTF-16. O idioma do
Android nao altera a formatacao.

Entradas continuam Float: nao ha precisao decimal ilimitada nem garantia de
todos os inteiros acima de 16.777.216. Casas decimais nao recuperam precisao
perdida. Projetos usam schema 9 para persistir a extensao; leitores antigos
devem recusar o schema, sem descartar silenciosamente os contadores.

Este documento reune instalacao, manifesto, permissoes, controles, leitura,
escrita, animacao, efeitos, referencias e ferramentas procedurais em tempo real.
Scripts de usuarios usam a mesma API publica dos exemplos: nao exigem acesso
nem privilegios de codigo-fonte. Detecte recursos com `beyz.supports`.

Contrato publico estavel: extensoes aditivas preservam a semantica existente;
quebras exigem uma nova `apiVersion`. A versao do app e a versao da API sao
independentes. Limites especificos estao descritos nas respectivas secoes.

## Indice

- [Deteccao de recursos](#detecção-de-recursos)
- [Pacote e manifesto](#pacote)
- [Parametros nativos](#parametros-nativos)
- [Ferramentas anexadas a objetos](#ferramentas-anexadas-a-objetos)
- [Entrada do script](#entrada)
- [Leitura](#leitura)
- [Escrita](#escrita)
- [Capacidades: propriedades, 3D, cameras, efeitos e keyframes](#capacidades)
- [Limites, stagger e restricoes](#limites-atuais)
- [Ferramentas procedurais em tempo real](#ferramentas-procedurais-em-tempo-real)
- [Apendice: repeticao de imagem](#apendice-repeticao-de-imagem)
- [Apendice: deformacao por pinos e Curvar](#apendice-deformacao-por-pinos-e-curvar)

## Detecção de recursos

Scripts não devem inferir capacidades apenas pelo número da API:

```javascript
beyz.api.version
beyz.api.features()
beyz.supports("paths.bindings.write")
```

Recursos v1 relevantes: `animation.read`, `keyframes.write`, `selection.write`,
`paths.geometry.read`, `paths.bindings.read`, `paths.bindings.write` e `parameters.conditional`.

Extensoes aditivas atuais: `properties.catalog`, `properties.evaluate`,
`transforms.3d.write`, `cameras.write`, `effects.keyframes.write`, `keyframes.batch`,
`timeline.frames`, `layers.timing`, `objectTools.declare`, `objectTools.attach` e
`objectTools.appearance`. O catalogo informa capacidades reais por canal; uma
feature global nao significa que qualquer propriedade de qualquer camada aceita
escrita ou animacao. Consulte dominio, tipo e operacoes disponiveis.

## Pacote

Um `.beyzscript` é um ZIP cuja raiz contém exatamente:

```text
manifest.json
main.js
```

Manifesto mínimo:

```json
{
  "schemaVersion": 1,
  "id": "com.seunome.meu-script",
  "name": "Meu script",
  "version": "1.0.0",
  "apiVersion": 1,
  "entry": "main.js",
  "author": "Seu nome",
  "description": "O que o script faz",
  "permissions": ["project.read", "selection.read", "layers.modify"],
  "parameters": [],
  "minimumSelectedLayers": 2,
  "maximumSelectedLayers": null
}
```

IDs iniciados por `com.beyz.` são reservados. O pacote descompactado não pode exceder 512 KiB.
Os limites de seleção são opcionais; o padrão aceita execução sem nenhuma layer selecionada.

O pacote pode ser empacotado como um ZIP com extensão `.beyzscript` contendo `manifest.json` e `main.js`,
ou importado diretamente como um arquivo `.js` solto.

No caso de um `.js` direto, metadados podem ser anotados no topo do arquivo:

```javascript
// ==BeyzScript==
// @name Meu Script
// @author Seu Nome
// @description O que o script faz
// @version 1.0.0
// @minSelection 1
// @maxSelection 5
// ==/BeyzScript==

function run(beyz, params) {
  // codigo
}
```

Se o arquivo `.js` não incluir cabeçalho de anotações, o Beyz infere o nome a partir do arquivo
(ex: `alinhar_centro.js` vira "Alinhar Centro"), gera um ID local e configura execução como Quick Action
(ação de 1 toque, sem parâmetros).

## Parametros nativos

O autor declara os controles no `manifest.json`; scripts nao desenham interfaces arbitrarias.

```json
"parameters": [
  {
    "id": "amount",
    "label": "Intensidade",
    "type": "Number",
    "defaultValue": 50,
    "minimum": 0,
    "maximum": 100
  },
  {
    "id": "enabled",
    "label": "Ativado",
    "type": "Boolean",
    "defaultValue": true
  },
  {
    "id": "mode",
    "label": "Modo",
    "type": "Enum",
    "defaultValue": "Caracteres",
    "options": ["Caracteres", "Palavras", "Linhas"]
  },
  {
    "id": "durationMs",
    "label": "Duração",
    "type": "Number",
    "defaultValue": 1000,
    "visibleWhen": {
      "parameterId": "mode",
      "equals": "Traçar caminho"
    }
  }
]
```

- `Number` com `integerOnly: true`: campo numerico de inteiros, mesmo com limites;
  valor vazio, fracionario, nao finito ou fora do intervalo nao permite executar.
  Use para contagens e intervalos em frames; o Stagger usa esse controle.
- `Number` com `minimum` e `maximum`, sem `integerOnly`: slider continuo;
- em ferramentas aplicadas, `Number` ligado a `procedural.ID`, com intervalo
  completo, usa regua horizontal e integracao com keyframes/curvas. A exibicao
  arredondada em inteiros nao altera a precisao interna; o teclado aceita decimais;
- `Number` sem intervalo completo: campo numerico;
- `Boolean`: chave binaria;
- `Enum`: caixa de escolha;
- `Color` e `Text`: campos validados pelo aplicativo.
- `LayerReference`: seletor de camada; entrega o ID ao script.
- `PathReference`: seletor de camada vetorial com caminho; entrega o ID ao script.

Os valores chegam tipados em `params`, por exemplo `params.amount`, `params.enabled` e `params.mode`.
`visibleWhen` controla a apresentação nativa; campos ocultos conservam seu valor padrão e não
exigem uma referência selecionada. Referências inexistentes, autorreferência e ciclos
entre condições são rejeitados na instalação.

## Ferramentas anexadas a objetos

Um script pode declarar botões e gavetas persistentes sem desenhar UI arbitrária. O aplicativo
renderiza os controles com a identidade visual do Beyz, valida valores e mantém undo/redo.

Controles numericos continuos de ferramentas anexadas usam um valor temporario
durante o arraste e confirmam uma unica alteracao ao soltar; cancelar descarta
o ajuste. Isso nao executa JavaScript durante o gesto nem cria, por si so, um
vinculo procedural. Canais nativos mantem a avaliacao existente descrita abaixo.

```json
"objectTools": [{
  "id": "my-controls",
  "label": "Meus controles",
  "icon": "tune",
  "parameters": [{
    "id": "opacity",
    "label": "Opacidade",
    "type": "Number",
    "defaultValue": 1,
    "minimum": 0,
    "maximum": 1,
    "nativePropertyPath": "opacity",
    "keyframeable": true
  }],
  "actions": [{ "id": "apply", "label": "Aplicar" }],
  "removeActionId": null
}]
```

```javascript
const attachmentId = beyz.tools.attach(layerId, 'my-controls');
beyz.tools.setValue(attachmentId, 'opacity', 0.75);
beyz.tools.updateAppearance(attachmentId, {
  label: 'Controle de opacidade',
  icon: 'opacity'
});
beyz.tools.listAttachments();
beyz.tools.detach(attachmentId);
```

O nome e o ícone também podem ser personalizados durante o anexo:

```javascript
const attachmentId = beyz.tools.attach(layerId, 'my-controls', {
  label: 'Ajustar camada',
  icon: 'tune'
});
```

`beyz.tools.icons()` retorna o catálogo estável de ícones semânticos disponível nesta versão.
Ícones desconhecidos usam `code` como fallback. A atualização é uma operação persistente e
participa do mesmo undo/redo atômico da execução do script; ela não deve ser chamada a cada
frame ou durante um gesto contínuo.

Ícones disponíveis: `code`, `polyline`, `tune`, `auto_fix_high`, `animation`, `layers`,
`account_tree`, `aspect_ratio`, `audio`, `blur_on`, `border_color`, `category`, `colorize`,
`curve`, `edit`, `effects`, `flip_horizontal`, `flip_vertical`, `gradient`, `group`, `image`,
`link`, `movie`, `music_note`, `null`, `opacity`, `palette`, `path`, `replace_media`,
`rounded_corner`, `shape`, `text` e `visibility`.

Ao tocar em uma ação, `run` recebe `params.__beyzToolAttachmentId`,
`params.__beyzToolId`, `params.__beyzToolAction` e os valores atuais em
`params.__beyzToolValues`.
`nativePropertyPath` conecta o controle a um canal avaliado pelo motor; somente esses canais podem
declarar `keyframeable: true`. A v1 suporta transformação/estilo listados pelo contrato e também
`path.progress`, `path.loop` e `path.autoOrient`. JavaScript não roda por frame.
Quando `removeActionId` referencia uma ação declarada, o botão de apagar executa essa ação; sem ela,
o comportamento padrão apenas desanexa a ferramenta. Isso permite limpeza transacional de vínculos
ou dados criados pelo script sem conceder UI ou código nativo exclusivo ao autor do aplicativo.

## Entrada

`main.js` deve declarar uma função global `run(beyz, params)`. Scripts sem parâmetros executam ao
tocar no card; scripts configuráveis abrem os controles nativos declarados no manifesto.

```javascript
function run(beyz, params) {
  const selected = beyz.selection.layers();
  selected.forEach((layer, index) => {
    beyz.layers.rename(layer.id, `Camada ${index + 1}`);
  });
}
```

As operações são acumuladas e devolvidas ao aplicativo. O script não altera o projeto durante a
execução. O Beyz valida e simula o plano completo antes de aplicá-lo como uma única ação de undo.
Parâmetros de cor usam `#RRGGBB` ou `#RRGGBBAA`.

## Leitura

```javascript
beyz.project.info()
beyz.composition.active()
beyz.timeline.playhead()
beyz.selection.layers()
beyz.selection.primary() // layer principal da selecao, ou null
beyz.selection.set([layerId], layerId) // lista selecionada e ID principal
beyz.layers.list()
beyz.layers.get(layerId)
```

Os objetos retornados são cópias do snapshot capturado no início. Alterá-los em JavaScript não muda
o projeto.

Uma layer expõe nesta versão:
- Identidade e estado: `id`, `name`, `type`, `visible`, `locked`, `blendMode`, `parentId`, `textContent`
- Transform no playhead: `positionX`, `positionY`, `scaleX`, `scaleY`, `rotation`, `opacity`, `anchorX`, `anchorY`
- Tempo na timeline: `startTimeMs`, `endTimeMs`
- Efeitos aplicados: `effects` (array de `{ id, typeId, enabled, parameters }`)
- Track Matte aplicado: `trackMatte` (`{ sourceLayerId: string, inverted: boolean }` ou `null`)
- Filhos de grupo: `childrenIds` (array de IDs quando `type === 'group'`)
- Vetores: `path.contours[].vertices[]`, com ID, posição, tangentes, modo e fechamento do contorno
- Animação: `animationChannels[]`, contendo `propertyPath`, tipo, valor padrão e keyframes
- Caminho animado: `pathKeyframes[]`, preservando todos os contornos e IDs de vértice

Cada keyframe de leitura pode conter interpolação, influências de entrada/saída, curva Bézier temporal,
resposta elástica e controles Bézier espaciais. Efeitos também expõem seus `animationChannels`.

```javascript
// Leitura de efeitos
beyz.effects.list(layerId)
```

## Escrita

```javascript
// Criação de Camadas (retornam o ID gerado)
const textId = beyz.layers.addText(text?, options?)
// options suporta: { name, x, y, width, height, color, startTimeMs, endTimeMs }
// Formas recebem nomes nativos distintos ("Retângulo 1", "Círculo 1", "Estrela 1", etc.)
const rectId = beyz.layers.addRectangle(width?, height?, cornerRadius?, options?)
const circleId = beyz.layers.addCircle(radius?, options?)
const ellipseId = beyz.layers.addEllipse(radiusX?, radiusY?, options?)
const starId = beyz.layers.addStar(options?)
const shapeId = beyz.layers.addShape(shapeType, options?) // ex: 'rectangle', 'circle', 'star', 'triangle', 'diamond', 'hexagon'
const nullId = beyz.layers.addNull(name?, options?)

// Identidade, visibilidade e bloqueio
beyz.layers.rename(layerId, name)
beyz.layers.setVisible(layerId, visible)
beyz.layers.setLocked(layerId, locked)
beyz.layers.setBlendMode(layerId, blendMode) // ex: 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color_dodge', 'color_burn', 'hard_light', 'soft_light', 'difference', 'exclusion'

// Transform (aplicado no playhead atual)
beyz.layers.setPosition(layerId, x, y)
beyz.layers.setRotation(layerId, degrees)
beyz.layers.setScale(layerId, scaleX, scaleY?) // se scaleY for omitido, usa uniforme
beyz.layers.setOpacity(layerId, opacity) // 0.0 a 1.0
beyz.layers.setAnchor(layerId, x, y)

// Tempo e Edição de Timeline
beyz.layers.setSpan(layerId, startTimeMs, endTimeMs)
beyz.layers.moveSpan(layerId, deltaMs) // contrato legado: desloca faixa e animacao
beyz.layers.offsetTiming(layerId, deltaMs, 'span' | 'keyframes' | 'both')
beyz.layers.timeline(groupId?) // ordem nativa da raiz ou filhos do grupo
const secondLayerId = beyz.layers.split(layerId, splitTimeMs?) // divide camada no tempo (ou playhead atual); Ctrl+Shift+D
beyz.layers.trimStart(layerId, newStartTimeMs?) // apara o início da camada no tempo (ou playhead)
beyz.layers.trimEnd(layerId, newEndTimeMs?) // apara o fim da camada no tempo (ou playhead)

// Gestão de Camadas
beyz.layers.duplicate(layerId, newName?)
beyz.layers.remove(layerId) // ou beyz.layers.delete(layerId)
beyz.layers.reorder(layerId, targetIndex)

// Parenting (Vinculação Pai-Filho)
beyz.layers.setParent(layerId, parentId, preserveWorldTransform?) // parentId = null para desvincular

// Track Matte (Máscara Alpha / Invertida)
beyz.layers.setTrackMatte(layerId, sourceLayerId, inverted?) // sourceLayerId = null para remover

// Grupos
const groupId = beyz.layers.group(layerIds, groupName?, customGroupId?)
beyz.layers.ungroup(groupId, preserveWorldTransform?)

// Efeitos
const effectId = beyz.effects.add(layerId, typeId, customEffectId?) // ex: 'gaussian_blur', 'hue_saturation', 'exposure_contrast', 'drop_shadow'
beyz.effects.setFloatParam(layerId, effectId, parameterId, value, timeMs?) // ex: 'radius', 'intensity', 'amount'
beyz.effects.setEnabled(layerId, effectId, enabled)
beyz.effects.remove(layerId, effectId) // ou beyz.effects.delete(layerId, effectId)

// hue_saturation: hue (-180..180), saturation (0..2), brightness (-1..1)
// exposure_contrast: exposure (-4..4), contrast (0..2)
// drop_shadow: blur, distance, angle e opacity; as instancias antigas radius/offset continuam legiveis
beyz.effects.reorder(layerId, effectId, targetIndex)

// Keyframes nativos
beyz.keyframes.setFloat(layerId, propertyPath, timeMs, value, options?)
beyz.keyframes.setVector(layerId, propertyPath, timeMs, x, y, options?)
beyz.keyframes.setColor(layerId, propertyPath, timeMs, color, options?)
beyz.keyframes.remove(layerId, propertyPath, timeMs)
beyz.keyframes.move(layerId, propertyPath, fromTimeMs, toTimeMs)

// options pode conter:
// { interpolation, cubicBezier, elastic, spatialBezier }

// Operações de Alto Nível
beyz.text.explode(layerId, mode, originalAction)

// Vínculos nativos entre vetores e objetos nulos
beyz.paths.createNulls(layerId, mode, options?)
// mode: 'points_follow_nulls', 'nulls_follow_points' ou 'trace_path'
// options: { durationMs, loop, autoOrient, contourIndex, controllerLayerId }
beyz.paths.listBindings()
beyz.paths.getBinding(bindingId)
beyz.paths.updateBinding(bindingId, { progress, timeMs, loop, autoOrient, contourIndex })
beyz.paths.toggleProgressKeyframe(bindingId, timeMs?)
beyz.paths.moveProgressKeyframe(bindingId, fromTimeMs, toTimeMs)
beyz.paths.setProgressCurve(bindingId, startTimeMs, {
  cubicBezier: { x1, y1, x2, y2 }
  // ou elastic: { intensity, frequencyHz, damping }
})
beyz.paths.removeBinding(bindingId, removeController?)
```

O exemplo de percurso usa as mesmas APIs públicas disponíveis para pacotes importados.
Ele nao acompanha mais o catalogo padrao do app; apenas Separar texto e incluido,
e tambem pode ser excluido. A exclusao persiste entre reaberturas e atualizacoes.
As APIs do exemplo continuam disponiveis:
cria o vínculo, anexa sua ferramenta declarativa e conecta seus controles aos canais `path.*`.
Projetos antigos sem anexo declarativo continuam abrindo pela interface legada de compatibilidade.
Sem `removeController`, `removeBinding` preserva a pose atual do nulo.

`beyz.paths.createNulls` exige uma camada vetorial Bézier. A operação cria os nulos e vínculos
persistentes em uma única transação. Esses vínculos são avaliados pelo motor nativo no preview e na
exportação; o JavaScript não é executado durante playback. Em `trace_path`, `durationMs` cria a
animação inicial de progresso, `loop` controla a repetição e `autoOrient` alinha o nulo à tangente.
Atualizar ou remover um vínculo também é uma transação nativa com undo. `removeController` é falso por
padrão para não apagar uma camada sem solicitação explícita.

Propriedades de keyframe graváveis na v1 incluem transformações (`transform.position`,
`transform.scale`, `transform.scaleX`, `transform.scaleY`, `transform.rotation`, `transform.anchor`),
opacidade, dimensões/estilo de forma, tipografia e volume. Canais apenas de leitura permanecem visíveis
no snapshot, mas uma tentativa de escrita não suportada é recusada antes de tocar o histórico.

Layers bloqueadas, IDs inexistentes, números não finitos, operações desconhecidas ou planos feitos
sobre uma revisão antiga são recusados integralmente.

As operações do lote são interpretadas em sequência contra o estado produzido pelas anteriores:
dois `moveSpan` relativos acumulam seus deslocamentos; IDs criados anteriormente ficam disponíveis,
e IDs removidos deixam de ser válidos. A seleção solicitada deve existir ao final do lote.
As APIs de leitura JavaScript continuam lendo o snapshot de entrada, não uma simulação do motor.

O commit verifica novamente projeto e revisão de forma atômica. Documento e seleção são publicados
juntos; falha não altera histórico nem autosave. Sair/trocar de editor, desativar ou remover o script
cancela a execução pendente. A preparação verifica cancelamento entre operações/comandos, e a
interrupção do runtime fecha o isolate. JavaScript não roda durante playback.

Diagnósticos nativos preservam a mensagem e acrescentam `code` e `operationIndex` (base zero,
opcional em erros globais). Entre os códigos estão `STALE_REVISION`, `PERMISSION_DENIED`,
`LAYER_LOCKED`, `INVALID_VALUE`, `INVALID_OPERATION`, `INVALID_SELECTION`,
`INCOMPATIBLE_API`, `OPERATION_LIMIT`, `INVALID_PLAN`, `TIMEOUT`, `SCRIPT_ERROR` e
`RUNTIME_FAILURE`. Esses diagnósticos não adicionam um canal de execução ao script.

Compatibilidade de efeitos v1: os presets nativos de hue/saturation, exposure/contrast, Gaussian
blur, tint, drop shadow, glow, copy background e invert têm defaults verificados contra o registro
do render. O fallback legado para outros IDs preserva o ID literal e parâmetros vazios; isso
**não garante suporte ao render**. IDs desconhecidos são recusados pelo registro, e efeitos como
Radiance exigem parâmetros que esse fallback não cria. Não se reinterpretam esses IDs como outro
efeito. Os catalogos de propriedades de camadas e parametros de efeitos estao
documentados abaixo; eles nao substituem um catalogo de presets nem garantem
a criacao de qualquer efeito por ID.

## Capacidades

### Catalogo de propriedades

Feature aditiva: `properties.catalog`.

```js
const properties = beyz.properties.list(layerId);
const position = properties.find(p => p.id === 'transform.position');
// position.setKeyframeOperation === 'keyframe.setVector', quando autorizado/editavel
```

Requer `project.read`. Retorna somente canais de animacao representados no snapshot
da camada solicitada, na mesma ordem; nao enumera propriedades de outras camadas.
Cada entrada informa `id`, `valueType`, `unit`, `minimum`/`maximum` (null quando
sem limite declarado), `readable`, `animatable`, `setValueOperation`,
`setKeyframeOperation` e `removeKeyframeOperation`.
O limite inferior e inclusivo, exceto quando `minimumExclusive` e true.

Os campos de operacao ficam null quando nao ha suporte na API atual, a camada esta
bloqueada ou falta `layers.modify`. `animatable` descreve o canal nativo, nao concede
permissao de escrita. Por exemplo, skewX e legivel/animavel nativamente, mas nao
gravavel por esta versao da API. Canais desconhecidos ou com tipo divergente ficam
somente de leitura, com unidade `native`; nao se adivinha uma operacao de escrita.

As unidades incluem px locais da composicao/camada, degrees, ratio, normalized e
rgba_hex. Metadados sao copias independentes. Nao ha avaliacao de cena, alteracao da
agulha ou mutacao de projeto ao consultar o catalogo. Permissoes e bloqueios sao
avaliados contra o snapshot de entrada; a validacao nativa continua obrigatoria.

### Transformacoes 3D e cameras

Features aditivas: `transforms.3d.write` e `cameras.write`.

```js
beyz.layers.setSpatialMode(layerId, '3d'); // '2d' para voltar; cameras nao aceitam 2d
beyz.layers.setGroupSpatialMode(groupId, 'preserve_3d'); // ou 'flattened'
beyz.layers.setPosition3D(layerId, x, y, z, timeMs);
beyz.properties.setFloat(layerId, 'transform.rotationX', degrees, timeMs);
beyz.properties.setFloat(layerId, 'transform.rotationY', degrees, timeMs);
beyz.properties.setFloat(layerId, 'transform.rotationZ', degrees, timeMs);
beyz.properties.setFloat(layerId, 'transform.scaleZ', ratio, timeMs);
beyz.properties.setFloat(layerId, 'transform.anchorZ', pixels, timeMs);
beyz.keyframes.setVector3(layerId, 'transform.position3D', timeMs, x, y, z, options);
beyz.keyframes.move(layerId, 'transform.position3D', fromMs, toMs);
beyz.keyframes.remove(layerId, 'transform.position3D', timeMs);
beyz.cameras.setNodeMode(cameraId, 'two_node', timeMs); // ou 'one_node'
beyz.cameras.setProjection(cameraId, 'perspective'); // ou 'orthographic'
beyz.cameras.setTarget(cameraId, x, y, z, timeMs);
beyz.properties.setFloat(cameraId, 'camera.verticalFov', 60, timeMs);
beyz.properties.setFloat(cameraId, 'camera.orthographicScale', 1, timeMs);
beyz.keyframes.setVector3(cameraId, 'camera.pointOfInterest', timeMs, x, y, z, options);
```

Setters com timeMs opcional usam o playhead quando omitido. Os tempos de escrita
sao tempos dos canais nativos, em ms; em grupos remapeados nao confundir tempo
local com tempo externo da composicao. XY/Z sao um keyframe editavel no modo 3D:
escrita, movimento e remocao usam os comandos que unificam os dois canais.
Os setters XY publicados continuam com a semantica v1, preservando Z em 3D.
Rotacao Z e um alias da rotacao existente; nao cria outro canal.
Posicao/ancora/alvo sao locais ao parentesco nativo; unidades em px, rotacoes
em graus e escala em razao. A API nao converte esses valores para coordenadas de tela.

O catalogo expoe canais espaciais nas camadas 3D e os canais especificos nas
cameras. FOV aceita 1..179 graus; escala ortografica deve ser estritamente positiva.
`transform.position3D` e uma vista integrada; os canais XY e Z originais tambem
continuam legiveis. Os valores solicitados usam seus avaliadores nativos.
Controles espaciais 2D nao sao aceitos na escrita Vector3. Curvas temporais e
elasticidade usam as mesmas opcoes dos outros keyframes.
Nao ha criacao/importacao de camera por esta extensao: ela edita cameras existentes.

### Parametros de efeitos

Aberracao cromatica schema 1: `chromatic_aberration` /
`motionlayer.chromatic_aberration`. Implementacao nativa inspirada no QCA 3,
nao uma replica das formulas proprietarias.

| Float animavel | Unidade e limites | Padrao |
| --- | --- | --- |
| channels | 0=R/B, 1=R/G, 2=G/B, 3=R, 4=G, 5=B; inteiro mais proximo | 0 |
| hue | graus, -3600..3600; gira a base de separacao, nao tinge a entrada | 0 |
| position_x / position_y | pixels locais, -10000..10000 | 0 |
| rotation | graus, -3600..3600 | 0 |
| scale | percentual, 1..1000 | 100 |
| skew | graus, -80..80; inclinacao horizontal | 0 |
| distortion | percentual, -25..25; distorcao cromatica por canal | 0 |
| lens_distortion | percentual, -100..100; distorcao radial comum da imagem inteira | 0 |
| anchor_x / anchor_y | percentual da caixa local, 0..100 | 50 |
| blur | raio gaussian em pixels locais, 0..500 | 0 |
| iterations | 1..16; inteiro mais proximo; mais iteracoes custam mais GPU | 1 |
| mix | percentual da entrada original, 0..100; 100 retorna a entrada | 0 |

Boolean: `repeat_edges=false` repete os pixels da borda real quando ligado;
`preserve_alpha=false` restaura cobertura original e tem precedencia sobre
`unmult=false` (Preto para alpha: cobertura pelo maior RGB premultiplicado);
`expand_buffer=true` permite franjas fora da caixa local. Desligar limita
a saida, sem descartar pixels necessarios a amostragem.
Flags nao sao canais animaveis. Os quatorze Float usam os setters, keyframes,
curvas temporais, undo e persistencia publicos existentes.

Escala, rotacao, inclinacao e lente usam a ancora do efeito; ela nao altera
a ancora da camada. No modo neutro nao ha passagem GPU extra. Matiz sozinha
tambem e neutra. Planos 3D processam em coordenadas locais antes da projecao;
camada de efeito e Capturar fundo processam a entrada composta. Nao cria
geometria RGB 3D independente. Buffer e qualidade permanecem sujeitos aos
limites reais de textura/memoria do aparelho.

`lens_distortion` deforma todos os canais juntos antes das transformacoes
cromaticas. Sozinha, nao cria franjas RGB nem depende de Canais/Iteracoes/Matiz.
Pode ser combinada com `distortion`; cada deformacao e monotona, aplicada
em sequencia, nao somada em um coeficiente que possa dobrar a imagem.
Zero preserva o resultado anterior. Em projetos antigos, sua ausencia
equivale a zero; o setter/keyframe publico materializa o canal ao editar.
Mix e preservar alpha continuam referenciando a entrada original do efeito.

```js
const chromatic = beyz.effects.add(layerId, 'chromatic_aberration');
beyz.effects.setFloatParam(layerId, chromatic, 'lens_distortion', -20, 0);
beyz.effects.setFloatParam(layerId, chromatic, 'distortion', 12, 0);
beyz.effects.setFloatParam(layerId, chromatic, 'iterations', 4, 0);
beyz.effects.setBooleanParam(layerId, chromatic, 'preserve_alpha', true);
beyz.effects.setFloatKeyframe(layerId, chromatic, 'position_x', 0, 0);
beyz.effects.setFloatKeyframe(layerId, chromatic, 'position_x', 1000, 8);
```

Varredura linear schema 1: `linear_wipe` / `motionlayer.linear_wipe`.
Quatro Float animaveis: `start`, `end`, `feather` em percentual 0..100;
`angle` em graus -3600..3600. Padroes: start=0, end=100, angle=90, feather=0.
Inicio >= Fim oculta totalmente; 0/100 conserva a camada inteira.
90 graus faz a varredura avancar da esquerda para a direita. A suavizacao
afeta somente a borda de transparencia, nao desfoca a imagem.
Usa setters/keyframes/curvas e permissoes nativos, com undo e persistencia.

```js
const wipe = beyz.effects.add(layerId, 'linear_wipe');
beyz.effects.setFloatParam(layerId, wipe, 'angle', 90, 0);
beyz.effects.setFloatParam(layerId, wipe, 'feather', 12, 0);
// Revelar em um segundo: tempo em ms, seguido do valor em percentual.
beyz.effects.setFloatKeyframe(layerId, wipe, 'end', 0, 0);
beyz.effects.setFloatKeyframe(layerId, wipe, 'end', 1000, 100);
```

### Varredura Circular

Use `circular_wipe` ou `motionlayer.circular_wipe` com `effect.add`.
Schema 1; parametros Float nativos, todos animaveis:

| Parametro | Padrao | Limites |
| --- | --- | --- |
| size | 100 | 0..100 (%) |
| center_x | 50 | -500..500 (% da largura local) |
| center_y | 50 | -500..500 (% da altura local) |
| feather | 0 | 0..100 (% do raio maximo) |
| invert | 0 | 0..1 (arredondado; 0 normal, 1 invertido) |

Use `effect.setFloatParam` e `effect.setFloatKeyframe`, inclusive para
`invert`. Nao usar `effect.setBooleanParam` nesse canal.
O circulo cresce pela distancia local em pixels, sem virar elipse por causa
da proporcao do projeto. 100% alcanca todos os cantos, mesmo com centro deslocado;
0% remove tudo. Inversao troca interior/exterior. Transformacoes nao uniformes
da camada continuam sendo respeitadas. Suavizacao modifica a borda, nao o alpha
original por inteiro. Camada de efeito/captura usam o pipeline nativo.
Requer as mesmas permissoes de efeitos/alteracao de camada das operacoes existentes.

### Sombra interna, Varredura radial, Separacao RGB e Espelho

Todos usam schema 1, aliases curtos abaixo e os equivalentes com prefixo
`motionlayer.`. Usam as mesmas permissoes, setters, keyframes, curvas e
transacoes de outros efeitos nativos. Nao exigem acesso ao codigo fonte.
Todos os parametros numericos sao Float animavel. Percentuais aqui usam
0..100, nao 0..1. Opcoes enumeradas sao arredondadas ao inteiro mais proximo.

| Efeito | Parametro | Padrao | Intervalo / unidade |
| --- | --- | --- | --- |
| `inner_shadow` | `blur` | 12 | 0..128 px |
| | `distance` | 12 | 0..256 px |
| | `angle` | 45 | -3600..3600 graus |
| | `opacity` | 65 | 0..100% |
| | `choke` | 0 | 0..128 px, erosao antes do blur |
| | `blend` | 1 | 0=Normal, 1=Multiplicar |
| | `color` | #000000 | Color animavel, alpha influencia a sombra |
| `radial_wipe` | `start` / `end` | 0 / 100 | 0..100% de uma volta |
| | `angle` | 0 | -3600..3600 graus, 0=topo |
| | `center_x` / `center_y` | 50 / 50 | -500..500% do elemento |
| | `feather` | 0 | 0..100% de suavizacao angular |
| | `direction` | 0 | 0=Horario, 1=Anti-horario, 2=Ambas |
| `rgb_split` | `distance` | 0 | 0..10000 px |
| | `angle` | 0 | -3600..3600 graus, 0=horizontal |
| | `channels` | 0 | 0=R/B, 1=R/G, 2=G/B |
| | `mix` | 0 | 0..100% do original |
| `mirror` | `mode` | 0 | 0=Reflexao, 1=Sobreposicao |
| | `center_x` / `center_y` | 50 / 50 | -500..500% do elemento |
| | `angle` | 0 | -3600..3600 graus, 0=esquerda/direita |
| | `opacity` | 100 | 0..100% |
| | `blend` | 0 | 0=Normal, 1=Multiplicar, 2=Screen, 3=Overlay, 4=Adicionar, 5=Diferenca |

No Espelho, `blend` atua apenas em Sobreposicao; Reflexao usa `opacity`
para interpolar com o original. Em RGB, o primeiro canal do par desloca
positivamente, o segundo negativamente; o terceiro fica parado.
Sombra interna conserva o alpha original. Varredura radial com Inicio >= Fim
apaga a copia processada, nao as camadas que estao abaixo de uma captura.
Os efeitos trabalham sobre raster, inclusive em 3D; nao criam novas geometrias.

```js
const shadow = beyz.effects.add(layerId, 'inner_shadow');
beyz.effects.setColorParam(layerId, shadow, 'color', '#102030');
beyz.effects.setFloatParam(layerId, shadow, 'opacity', 75, 0);
const radial = beyz.effects.add(layerId, 'radial_wipe');
beyz.effects.setFloatKeyframe(layerId, radial, 'end', 0, 0);
beyz.effects.setFloatKeyframe(layerId, radial, 'end', 1000, 100);
const split = beyz.effects.add(layerId, 'rgb_split');
beyz.effects.setFloatKeyframe(layerId, split, 'distance', 0, 0);
beyz.effects.setFloatKeyframe(layerId, split, 'distance', 1000, 12);
const mirror = beyz.effects.add(layerId, 'mirror');
beyz.effects.setFloatParam(layerId, mirror, 'mode', 1, 0);
beyz.effects.setFloatParam(layerId, mirror, 'opacity', 50, 0);
```

Substituir cor schema 1: `replace_color` / `motionlayer.replace_color`.
`from` e `to` sao Color animavel (padroes #00FF00 e #1891F4).
`tolerance`, `softness`, `amount` sao Float animavel 0..1
(padroes 0.1, 0.05, 1). `comparison` aceita 0=RGB, 1=matiz, 2=crominancia;
`method` aceita 0=direta, 1=preservar variacoes (padrao). Os dois modos sao
canais Float, arredondados ao inteiro mais proximo pelo shader.
A substituicao preserva o alpha da entrada; alpha das cores de controle e ignorado.
Mascara/Original sao apenas inspecao do editor, nao efeitos publicos.

```js
const replace = beyz.effects.add(layerId, 'replace_color');
beyz.effects.setColorParam(layerId, replace, 'from', '#00FF00');
beyz.effects.setColorParam(layerId, replace, 'to', '#1891F4');
beyz.effects.setFloatParam(layerId, replace, 'tolerance', 0.15, 0);
beyz.effects.setFloatParam(layerId, replace, 'method', 1, 0);
beyz.effects.setFloatKeyframe(layerId, replace, 'amount', 0, 0);
beyz.effects.setFloatKeyframe(layerId, replace, 'amount', 1000, 1);
```

Chroma Key schema 1: `chroma_key` / `motionlayer.chroma_key`.
Os canais Float animaveis `threshold`, `softness`, `spill`, `clip_black` e
`clip_white` usam 0..1, nao os percentuais 0..100 exibidos pela interface.
Padroes: 0.1, 0.05, 0, 0 e 1, respectivamente. `color` e Color animavel,
verde `#00FF00` por padrao; `invert` e Boolean estatico, false por padrao.
Spill reduz a componente cromatica da cor-chave sem alterar alpha, e nao opera
com inversao ativa. Suavidade e transicao de cor, nao desfoque espacial.
Os modos Mascara/Original sao somente inspecao do editor, nao parametros publicos.

```js
const key = beyz.effects.add(layerId, 'chroma_key');
beyz.effects.setColorParam(layerId, key, 'color', '#00FF00');
beyz.effects.setFloatParam(layerId, key, 'threshold', 0.15, 0);
beyz.effects.setFloatKeyframe(layerId, key, 'softness', 0, 0.05);
beyz.effects.setFloatKeyframe(layerId, key, 'softness', 1000, 0.12);
beyz.effects.setBooleanParam(layerId, key, 'invert', false);
```

Usa as permissoes e protecoes existentes das operacoes de efeitos; nao exige
acesso ao codigo fonte. Cor, valores e keyframes persistem e participam de undo.

Efeito nativo adicional: image_repeat / motionlayer.image_repeat. Criacao pelo
fluxo publico de efeitos inclui tile_x/tile_y=100, output_x/output_y=300,
shift_x/shift_y=0, spacing_x/spacing_y=0, tile_angle=0, alternate_offset=0,
mirror=false e alternate_vertical=false. Os dez Float sao animaveis; tile_angle
usa graus, os demais percentuais. mirror e alternate_vertical sao Boolean
estaticos. Schema 2 aceita efeitos legados schema 1 com novos valores neutros.
Limites e comportamento: [contrato de repeticao de imagem](#apendice-repeticao-de-imagem).

Efeitos de deformacao schema 1: `bend` / `motionlayer.bend` e
`puppet` / `motionlayer.puppet`. Curvar inicia neutro, com angle=0, start=0,
tune_head=75, tune_tail=25, origin=3 (base), before=0 (fixo).
Angle usa graus; start e ajustes de extremidade usam percentuais.
Origin: 0 esquerda, 1 direita, 2 topo, 3 base. Before: 0 fixo,
1 mesmo lado, 2 lado oposto. Na interface esses dois parametros sao seletores.

Puppet inicia sem pinos. A criacao nao exige acesso ao codigo fonte:

```js
const puppet = beyz.effects.add(layerId, 'puppet');
const foot = beyz.effects.addPuppetPin(layerId, puppet, 50, 90);
const hand = beyz.effects.addPuppetPin(layerId, puppet, 80, 20);
beyz.effects.setFloatKeyframe(layerId, puppet, `pin${hand}_x`, 0, 80);
beyz.effects.setFloatKeyframe(layerId, puppet, `pin${hand}_x`, 1000, 95);
// beyz.effects.removePuppetPin(layerId, puppet, hand);
```

`addPuppetPin(layerId, effectId, x, y, requestedSlot?)` retorna o slot numerico,
de 0 a 15. X/Y de montagem sao percentuais locais 0..100, nao coordenadas da tela.
Slot ativo ou ponto de montagem duplicado e recusado. Destinos `pinN_x` e
`pinN_y` sao canais Float animaveis, -500..500%. Excluir um pino nao renumera
os outros; reutilizar um slot apagado cria uma montagem nova, sem herdar animacoes.
Permissao: `effects.modify`; camadas bloqueadas continuam protegidas.
Deformacao por textura nao e rigging de um solido extrudado nem o Advanced
Puppet completo. Contrato, algoritmo e limites: [deformacao por pinos e Curvar](#apendice-deformacao-por-pinos-e-curvar).

Feature aditiva: `effects.keyframes.write`.

```js
beyz.effects.properties(layerId, effectId);
beyz.effects.setFloatKeyframe(layerId, effectId, parameterId, timeMs, value, options);
beyz.effects.setVectorKeyframe(layerId, effectId, parameterId, timeMs, x, y, options);
beyz.effects.setColorKeyframe(layerId, effectId, parameterId, timeMs, '#12345680', options);
beyz.effects.moveKeyframe(layerId, effectId, parameterId, fromMs, toMs);
beyz.effects.removeKeyframe(layerId, effectId, parameterId, timeMs);
beyz.effects.setColorParam(layerId, effectId, parameterId, '#12345680', timeMs);
beyz.effects.setBooleanParam(layerId, effectId, parameterId, true);
```

O catalogo do efeito informa capacidades por parametro existente, inclusive
pontos Vector2 das curvas RGB. Float, Vector2 e Color usam canais nativos animaveis;
Boolean continua estatico. `setColorKeyframe` aceita as mesmas opcoes temporais
dos outros keyframes (linear/hold/ease/Bezier). Cores interpolam os componentes
RGBA com o avaliador nativo usado no preenchimento; preview/export compartilham
essa avaliacao. `setColorParam` sem keyframes altera o valor base; com keyframes,
atualiza/insere no tempo indicado, sem apagar os demais. `timeMs` e opcional na
funcao JavaScript e assume o playhead. Cores dos efeitos ignoram alpha quando
o contrato especifico do efeito trabalha somente com RGB, como Chroma Key.
Uma tentativa com tipo errado, parametro inexistente ou camada
bloqueada e recusada na validacao do plano. Permissao de escrita: effects.modify
ou layers.modify, preservando a compatibilidade v1.
Parametros usam a unidade nativa do efeito; nao ha conversao generica para porcentagem.
Criar efeito desconhecido conserva o fallback v1; isso nao garante suporte de render
nem cria parametros que o motor nao possui.

### Leitura nativa em tempo solicitado

Feature aditiva: `properties.evaluate`. Declare no manifesto:

```json
"evaluationTimesMs": [0, 500, 1000]
```

```js
const value = beyz.properties.valueAt(layerId, 'transform.position3D', 500);
const blur = beyz.properties.valueAt(layerId, 'effects.blur.radius', 500);
const localTime = beyz.timeline.layerTime(layerId, 500);
```

Requer project.read. Os tempos declarados sao externos da composicao; o factory
aplica o remapeamento nativo de grupos antes de avaliar cada canal. layerTime
retorna esse tempo local, ou null quando o mapeamento nativo indica um grupo
fora da faixa. Valores de canal ainda podem ser lidos fora de sua faixa visivel:
isso nao significa que a camada seria desenhada naquele tempo.
A leitura usa o estado capturado antes do plano, nao as escritas ainda acumuladas.
Nao move a agulha, nao roda expressoes por frame e nao replica interpolacao em JS.
Valores vetoriais retornados sao copias.

Maximo de 16 tempos distintos, nao negativos e dentro da duracao da composicao.
Limite conservador de 50.000 leituras estimadas por snapshot. Tempo nao declarado,
propriedade ausente ou pedido excessivo e recusado, sem avaliacao silenciosa aproximada.
Tempos dinamicos nao declarados durante run nao sao suportados nesta extensao.
Sem evaluationTimesMs, nao ha amostragem extra nem campos de valores vazios no JSON.

### Keyframes em lote

Features: `keyframes.batch` e `timeline.frames`. Extensao aditiva; os setters,
move/remove individuais v1 conservam seus contratos anteriores.

```javascript
function run(beyz) {
  const keys = [100, 200].map(timeMs => ({
    layerId: 'source', propertyPath: 'transform.position', timeMs
  }));
  const clipboard = beyz.keyframes.copy(keys);
  beyz.keyframes.offset(keys, beyz.timeline.framesToMs(6));
  beyz.keyframes.paste(clipboard, [
    {sourceLayerId: 'source', targetLayerId: 'target'}
  ], 500);
  beyz.keyframes.removeBatch([
    {layerId: 'source', propertyPath: 'transform.position', timeMs: 200}
  ]);
}
```

Referencia de propriedade: `{layerId, propertyPath, timeMs}`.
Referencia de efeito: `{layerId, effectId, parameterId, timeMs}`;
nao misturar ambos os enderecos. Float, Vector2, cores nativas, alvo Vector3
da camera, shape.path e alinhamento do contorno usam seus canais tipados.
Booleanos de efeitos continuam estaticos; cores possuem canais nativos,
inclusive para copiar/colar, mover e remover keyframes.

- `copy(keys, options?)`: exige project.read e retorna um identificador opaco.
  Captura os keyframes NATIVOS no ponto dessa operacao no plano sequencial.
  Inclui escritas anteriores e nao inclui escritas posteriores. Nao copia via JSON,
  nao altera o documento e nao cria undo quando usada sozinha.
- `paste(clipboard, destinations, atTimeMs, options?)`: ancora o primeiro tempo
  copiado em atTimeMs e conserva as distancias entre chaves/camadas.
  Cada origem precisa de ao menos um destino; permite varios destinos por origem.
  Mapeamentos repetidos, origens extras ou duas chaves entrando no mesmo frame
  do mesmo canal sao recusados, inclusive em modo replace.
- `offset(keys, deltaMs, options?)`: le todas as origens antes de mover;
  chaves adjacentes selecionadas nao sobrescrevem umas as outras.
- `removeBatch(keys, options?)`: remove somente as chaves logicas selecionadas.

O clipboard existe somente nessa execucao. Nao e o clipboard do Android e
nao e persistido. Uma chave inexistente, destino bloqueado/incompativel ou
operacao invalida recusa o plano inteiro antes do commit.
Escritas exigem layers.modify; effects.modify sozinho autoriza somente canais
de efeitos. A permissao e conferida para todos os destinos, nao apenas o primeiro.

Opcoes:

```javascript
{conflict: 'error', timeReference: 'channel'} // defaults
{conflict: 'replace', timeReference: 'composition'}
```

Conflito usa a identidade por frame nativa no FPS da composicao, nao apenas
igualdade de milissegundos. error recusa um frame ocupado por uma chave nao
selecionada para o deslocamento. replace substitui esse frame explicitamente.
Nenhuma opcao corta ou ajusta silenciosamente o deslocamento ao limite.
Tempos finais devem permanecer em 0..duracao da composicao.

channel significa os tempos armazenados nos canais nativos, NAO tempo relativo
ao inicio da faixa. composition usa essa mesma referencia absoluta somente
quando a cadeia de grupos nao tem remapeamento. Para grupos retimed, a escrita
composition e recusada porque loop/freeze/stretch nao oferecem uma inversa
unica que preserve curvas e distancias. Use channel; leituras externas continuam
disponiveis pela avaliacao nativa.

Posicao XY/Z e uma chave logica, incluindo dados antigos divididos. Copiar,
mover ou apagar uma delas inclui os dois canais quando presentes. Colagem de
posicao entre camadas 2D/3D incompatíveis e recusada; nao inventa Z nem deixa
chaves orfas. Pontos de uma mesma curva RGB no mesmo frame sao um grupo nativo.
Efeitos no destino usam ID correspondente ou tipo + ordinal nativo, com mesmo
parametro e tipo de valor. Nao cria efeitos ausentes automaticamente.

Metadados preservados integralmente: interpolacao, easingIn/Out, cubicBezier,
elasticResponse e spatialBezier. Paths conservam contornos, IDs de vertices,
tangentes, estados aberto/fechado e dimensoes; nao sao escalados automaticamente.
Undo/redo continua sendo unico por plano, mesmo com varias origens/destinos.

`framesToMs(frames)` e `msToFrames(timeMs)` usam FPS inteiro da composicao,
arredondamento ao inteiro mais proximo e permitem deslocamentos negativos.
Fracoes de frame, NaN/Infinity e valores que excedem precisao inteira exata
sao recusados. A conversao nao muda a agulha.

Limites: ate 10.000 referencias/mapeamentos por operacao e 10.000 chaves nativas
processadas acumuladas por plano em copy/offset/remove/paste, incluindo expansao
XYZ/curvas e multiplicacao por destinos. As operacoes em lote tambem contam no
limite v1 de operacoes. Cancelamento e conferido durante o processamento.

### Permissoes

- `project.read`: projeto, composição e lista de layers;
- `selection.read`: layers selecionadas;
- `selection.modify`: altera a selecao depois que o plano for aplicado;
- `layers.modify`: operações de escrita em camadas;
- `effects.modify`: adição, remoção, ordenação e configuração de parâmetros de efeitos.

Pacotes locais pedem consentimento antes da primeira execução. Alterar o conteúdo do pacote muda o
hash e exige novo consentimento.

Ferramentas anexadas persistem ID, versao, controles e valores no projeto, nao
codigo executavel. A interface resolve a definicao pela mesma ID e versao do
script instalado. Trocar a versao ou remover o pacote nao migra silenciosamente
uma ferramenta existente; seus dados persistidos permanecem no projeto.

## Limites atuais

### Stagger e deslocamento explicito

A feature `layers.timing` oferece `layers.offsetTiming(id, deltaMs, mode)` com
`span`, `keyframes` ou `both`, sem modo implicito. Delta deve ser inteiro em ms.
`span` move a faixa sem mudar o trecho da midia ou as keys; `keyframes` move
somente keys; `both` move os dois, preservando curvas e canais nativos.
A composicao pode crescer ao mover a faixa; nao e encurtada automaticamente.
Tempos negativos, overflow e keys fora da duracao resultante sao recusados.
Grupos com filhos, ancestrais retimed e path bindings exigem edicao coordenada
e sao explicitamente recusados nesta operacao. `moveSpan` v1 nao mudou.

`layers.timeline(groupId?)` retorna a ordem nativa da raiz ou filhos do grupo.
Requer `project.read`; nao equivale a `layers.list()` em projetos reordenados.

O exemplo `com.beyz.stagger` usa somente essas APIs publicas e nao vem instalado
como padrao. Selecionar
pelo menos duas camadas, escolher intervalo inteiro em frames, ordem de selecao
ou timeline, inversao e modo. Atrasa cada item relativamente ao tempo atual;
o primeiro nao se move. Arredonda o total de frames de cada item, nao cada passo.
Ordem de timeline pede selecao em um mesmo container; grupos sao editados pelos
filhos. Toda a execucao aplica um unico lote, undo e agendamento de autosave.
O Hub permite cancelar antes do commit, sem publicacao de resultados tardios.

### Restricoes gerais

- execução manual e serial;
- timeout de 2 segundos;
- heap alvo de 32 MiB quando suportado pelo WebView;
- retorno máximo de 2 MiB;
- até 10.000 operações por plano;
- requisitos de seleção validados pela interface e novamente pelo runtime;
- sem rede, arquivos, clipboard do sistema, intents, código Android, código nativo ou WebAssembly;
- parâmetros `number`, `boolean`, `enum`, `color` e `text` possuem controles gerados pelo app;
- automações e expressões JavaScript ainda não fazem parte desta API; vínculos de caminho são
  constraints nativas persistentes e não expressões executadas por frame.

## Ferramentas procedurais em tempo real

### Contrato

Um script configura um programa declarativo em uma transacao. O motor nativo
avalia esse programa durante o arraste, playback, scrub e exportacao.
JavaScript nao roda por quadro nem ao abrir o projeto.
Nao ha callbacks JavaScript arbitrarios de comportamento nesta API.

Permissoes: escrita exige layers.modify; leitura de referencias e conversao
do quadro atual exigem project.read. Cada controlador pertence ao script que
o criou: outro script nao pode sobrescrever, editar ou remover esse controlador.
Camadas bloqueadas, conflitos de escritores e ciclos sao recusados.
Um lote invalido nao altera parcialmente o projeto.

### Metodos

```javascript
beyz.supports("procedural.read")
beyz.supports("procedural.write")
beyz.supports("procedural.effects")
beyz.supports("objectTools.references")
beyz.procedural.list() // copias completas dos programas do snapshot
beyz.procedural.get(controllerId)
beyz.procedural.put(program) // cria ou substitui explicitamente, retorna o ID
beyz.procedural.setValue(controllerId, parameterId, 90, timeMs)
beyz.procedural.setValue(controllerId, vectorParameterId, [10, 20, 0], timeMs)
beyz.procedural.setEnabled(controllerId, false)
beyz.procedural.remove(controllerId)
beyz.procedural.bakeCurrent(controllerId, timeMs)
```

Tempo omitido usa a agulha do snapshot. setValue usa tempo de composicao e o
mapeamento temporal nativo do host. Canais sem animacao alteram o valor base;
canais animados usam a semantica nativa de withValueAt, preservando curvas.
Leituras dos canais refletem o snapshot (ou um put planejado), nao amostram
novamente o resultado de setValue antes de confirmar o lote.
put substitui o programa completo: preserve channels/keyframes obtidos por get
se a intencao for apenas reconstruir referencias. Nao substitua animacao por
valores padrao inadvertidamente.
bakeCurrent converte apenas a amostra atual em valores/canais base e remove
o controlador. Nao converte uma animacao inteira.

### Programa

```javascript
const attachmentId = beyz.tools.attach(hostId, "wheel");
beyz.procedural.put({
  id: attachmentId,
  attachmentId,
  hostLayerId: hostId,
  version: 1,
  enabled: true,
  parameters: [{
    id: "phase",
    type: "Number",
    channels: [{
      defaultValue: 0,
      keyframes: [
        {timeMs: 0, value: 0, interpolation: "Ease",
         cubicBezier: [0.2, 0.8, 0.8, 1]},
        {timeMs: 1000, value: 360, interpolation: "Linear"}
      ]
    }]
  }],
  nodes: [{
    id: "phase",
    operation: "Parameter",
    type: "Number",
    parameterId: "phase",
    inputs: []
  }],
  outputs: [{
    reference: {layerId: targetId, propertyPath: "transform.rotation"},
    nodeId: "phase",
    mode: "Add"
  }]
});
```

Tipos: Number, Boolean, Vector2, Vector3. channels tem respectivamente 1, 1, 2
ou 3 canais escalares. Boolean aceita 0/1, avaliado em degraus, nao interpolado.
Keyframes: timeMs, value, interpolation (Linear/Hold/Ease), cubicBezier de quatro
numeros e elastic [intensity, frequencyHz, damping]. A animacao usa tempo do host.

Operacoes: Constant, Parameter, Read, Add, Subtract, Multiply, Divide, Negate,
Sin, Cos, Vector2, Vector3, Component, Length, Minimum, Maximum, Clamp, Select,
WorldToLocalPosition. Seno/cosseno usam graus. Entradas referenciam IDs de nodes.
Extensao negociada com beyz.supports("procedural.space.localToWorld"):
LocalToWorldPosition e LocalToWorldDirection, entradas/saida Vector3 e referencia
{layerId, propertyPath: "transform.position"}. readMode Base/Effective escolhe
transformacao original/dirigida. Position recebe um deslocamento relativo a ancora
da propria camada e retorna um ponto mundial. Direction transforma um vetor com
rotacao/escala, sem translacao, sem normalizar e sem tratar como normal de superficie.
Ambas incluem os pais e o tempo local, usam matrizes do motor e cache por amostra;
Effective declara dependencias dos escritores na cadeia e rejeita ciclos e path
followers ainda nao suportados. WorldToLocalPosition conserva seu contrato antigo:
converte um ponto mundial para a posicao no espaco do PAI da camada destino,
nao e o inverso de LocalToWorldPosition para a mesma camada.
Constant usa constant: [valores]; Parameter usa parameterId; Component usa component.
Read usa reference e readMode Base/Effective. Select recebe Boolean, valor true,
valor false, todos tipados. WorldToLocalPosition recebe Vector3 de mundo e uma
reference para transform.position da camada destino; usa o inverso do pai efetivo.
Singularidade, divisao por zero e valores nao finitos geram diagnosticos.

Propriedades: transform.position e transform.scale (Vector3), transform.rotation,
transform.rotationX, transform.rotationY e opacity (Number).
world.position e uma leitura Vector3 da ancora em mundo, antes da camera.
Saida Replace substitui o resultado avaliado; Add soma ao canal base.
Keyframes base da camada nao sao apagados.
Parametros Float e Vector2 de efeitos existentes aceitam leitura e saida procedural
quando `beyz.supports("procedural.effects")` retorna true. Consulte Parametros De Efeito
para tipos, permissoes e limites; nao ha suporte generico a qualquer tipo de parametro.
Orientacao mundial por quaternion e espaco de tela nao sao suportados.
Leituras Effective em mundo de path followers sao recusadas explicitamente.

Limites: 128 controladores, 4096 nodes, 1024 saidas e 65536 keyframes por
composicao; por controlador, 1024 nodes e 256 parametros; por canal, 4096 keys.
Grafo/dependencias tambem sao limitados. Eles nao representam metas de desempenho.

### Controles E Referencias

Declare objectTools.parameters como na API existente:

```json
{
  "id": "rotation",
  "label": "Rotacao",
  "type": "Number",
  "defaultValue": 0,
  "minimum": -36000,
  "maximum": 36000,
  "nativePropertyPath": "procedural.phase",
  "keyframeable": true
}
```

O programa ligado a ferramenta usa id == attachmentId. Seu parametro phase
deve existir e ter o mesmo tipo do controle (Number ou Boolean).
Cada arraste e uma sessao: preview transitorio, uma confirmacao, um undo.
Cancelar restaura o estado anterior; nao ha autosave por movimento.
Os mesmos canais aparecem na timeline expandida para selecionar, mover,
copiar, colar e excluir keyframes, usando o editor de curvas existente.
Ao colar em outro host, e necessario um unico parametro correspondente:
mesmo ID/componente/tipo. Destinos ausentes ou ambiguos sao recusados.

Tipos novos de referencia:

- LayerListReference: array JSON de IDs distintos, maximo 256 e 32768 caracteres.
- PropertyReference: {layerId, propertyPath}.
- EffectReference: {layerId, effectId, parameterId}; parametros Float/Vector2.
- LayerReference e PathReference permanecem strings de ID.

referenceLayerTypes filtra shape/text/image/video/audio/null/camera/group/adjustment.
A UI de referencia de efeito apresenta parametros Float e Vector2 existentes,
incluindo offsets e pontos de curvas RGB. Boolean e Color nao sao alvos procedurais.
Valores estruturados podem ser passados diretamente a tools.setValue:

```javascript
beyz.tools.setValue(attachmentId, "cabins", [firstId, secondId]);
beyz.tools.setValue(attachmentId, "source",
  {layerId: firstId, propertyPath: "transform.position"});
```

listAttachments retorna values como strings persistidas. Use JSON.parse para
listas/objetos. Alterar selecao nao altera referencias. Mudancas de referencia
sao explicitas: a acao do script pode reconstruir o programa preservando canais.
O exemplo usa o botao Aplicar referencias. Falha nessa acao nao publica grafo
invalido; o programa anterior continua preservado.

Duplicar o grupo contendo host/alvos religa os IDs internos e conserva animacao.
Duplicar somente o host conserva referencias externas, mas desativa o clone
para nao disputar os alvos da ferramenta original. Textos livres nao sao
reinterpretados como IDs. A gaveta mostra o estado desativado.

### Exemplo Roda-Gigante

O exemplo Roda-Gigante usa o ID local.roda-gigante, importado pelo mesmo
fluxo dos usuarios, sem namespace reservado.
Nao adiciona efeitos, cabines ou keyframes de demonstracao. A configuracao inicial
mostra apenas nulo e cabines; depois de aplicar, raio, rotacao, distribuicao,
escala das cabines e orientacao ficam na ferramenta do nulo escolhido.
O script permite escolher nulo central e 1-64 cabines, controlar raio, rotacao,
distribuicao angular e manter orientacao, com referencias estaveis.
O circulo ocupa o plano XY mundial; posicionamento e convertido para o espaco
local de cada cabine. Manter orientacao preserva a rotacao local base das
cabines, nao promete compensacao quaternion de pais 3D.
Uma implementacao com LocalToWorldPosition permite que o plano acompanhe
rotacao X/Y/Z, escala e pais do nulo; todos os controles continuam publicos.
Reaplicar referencias preserva os canais e curvas existentes.
Escala das cabines multiplica a escala base de todas as cabines: 100% preserva,
50% aplica fator 0.5 e 200% aplica fator 2, sem mudar o raio nem apagar animacoes.
Importar a nova versao e aplicar ao mesmo nulo migra a ferramenta preservando
seus canais existentes. O pacote usa apenas a API publica e permissoes normais.

### Parametros De Efeito

Detecte suporte com beyz.supports("procedural.effects"). Para Read e outputs,
o endereco e {layerId, propertyPath: "effects." + effectId + "." + parameterId}.
Use IDs persistidos, nunca o indice do efeito na lista.

```javascript
outputs: [{
  reference: {layerId: targetId, propertyPath: "effects." + blurId + ".radius"},
  nodeId: "intensity",
  mode: "Replace"
}]
```

Float corresponde a Number; Vector2 corresponde a Vector2. O efeito e o
parametro devem existir, ter schema suportado e tipo compativel com o catalogo
nativo. Boolean, Color, parametros ausentes e tipos desconhecidos sao recusados
na instalacao do programa. Nao ha criacao implicita de parametros opcionais.

Os limites Float sao exatamente os do renderizador: a amostra final e limitada
ao intervalo definido, inclusive depois de Add; leitores Effective veem esse
mesmo valor. Exemplo: raio gaussiano entre 0 e 500. Vetores mantem as regras
originais do efeito, sem um intervalo numerico inventado. Base usa os canais
originais, curvas legadas de efeito e tempo local do alvo; Effective inclui o
vinculo. Reordenar preserva o alvo. Duplicar religa IDs de efeitos duplicados.
Desativar restaura os canais originais; bakeCurrent insere apenas a amostra no
canal original e preserva os outros keyframes.

O desfoque por distancia pode ser construido com os contratos publicos abaixo.
Escolha um nulo e o parametro radius de um desfoque gaussiano ja aplicado.
A distancia entre as ancoras mundiais do nulo e da camada, incluindo Z e pais,
e multiplicada pelo controle animavel Desfoque por pixel. Nao e distancia de
tela/camera nem distancia de superficie. Selecionar outra camada nao religa
o alvo. Aplicar referencias reconstrui o vinculo preservando animacao/curvas.
Referencias World Effective de path followers permanecem nao suportadas.

### Controles E Keyframes Na Interface

Toque no controle ou arraste sua regua para ativar o parametro. O botao de
keyframe da gaveta atua nesse parametro, nao em uma propriedade nativa ficticia.
Os marcadores procedurais aparecem na faixa da camada e nas propriedades
expandidas; os do parametro ativo recebem destaque e os demais ficam fantasma.
Um arraste prolongado no marcador ativo move seu tempo. A navegacao anterior/
seguinte tambem inclui keyframes procedurais. As reguas numericas do script
aceitam arraste horizontal; rolar a gaveta verticalmente nao altera o valor.
Valores numericos sao exibidos como inteiros sem quantizar os canais Float.
A edicao por teclado conserva acesso a decimais. Isso nao muda curvas ou exportacao.

A ferramenta Desfoque do exemplo e um controle criado pelo script, nao uma
nova gaveta nativa de efeitos. Seu ganho dirige o raio do efeito por distancia.
Em Replace, editar o raio base do efeito nao substitui a saida dirigida:
ajuste o ganho da ferramenta ou desative o vinculo para usar o canal base.
Os dois conjuntos de keyframes permanecem preservados.

### Persistencia E Confianca

O .beyzmd preserva controlador, referencia, canais, ferramenta e dependencias
privadas de script. Codigo importado nao herda confianca nem aprovacao por ter
o mesmo ID de um script oficial. Abrir/avaliar o programa declarativo validado
nao executa JavaScript. Acionar acoes do script continua exigindo aprovacao.
Referencia removida nao e substituida automaticamente por outra camada.
Remover um efeito ou tornar seu parametro incompativel tambem preserva o
programa e gera MISSING_EFFECT_REFERENCE. O vinculo afetado nao e aplicado;
os canais base permanecem intactos. Repare as referencias explicitamente ou
desative/remova o vinculo antes de exportar. Falha numa nova configuracao
nao modifica o documento nem publica uma parte do lote.
Exportacao de um programa ativo invalido e recusada, nao mascarada.

## Apendice: repeticao de imagem

Implementada em 2026-10-05. Tipo persistido: motionlayer.image_repeat, schema 2.
Os projetos com schema 1 continuam aceitos: parametros novos ausentes valem zero
ou false, conservando o caminho de amostragem e a aparencia anteriores.
Repete os pixels renderizados de uma camada, nao cria camadas ou objetos 3D.
No conteudo 3D, a repeticao atua sobre a imagem projetada completa, inclusive
volume. As copias nao possuem profundidades ou transformacoes independentes.

### Controles

- tile_x / tile_y: tamanho da copia em percentual do retangulo da imagem original;
  100 preserva o tamanho; intervalo 1..1000.
- output_x / output_y: area de saida centrada na imagem, em percentual;
  padrao 300 (area de tres larguras/alturas), intervalo 0..2000.
- shift_x / shift_y: deslocamento do padrao em percentual do tamanho original,
  sem mover a area de saida; intervalo -10000..10000.
- mirror: espelha copias alternadas nas duas direcoes. Booleano estatico.
- spacing_x / spacing_y: espaco transparente entre copias, percentual do tamanho
  original (nao do tamanho reduzido da copia). Padrao 0, intervalo 0..1000.
- tile_angle: rotacao individual em graus, padrao 0, intervalo -1800..1800.
  Cada celula acomoda o retangulo envolvente da copia girada: nao corta seus
  cantos nem gira a grade inteira. O espacamento e acrescido a esse retangulo.
- alternate_offset: deslocamento das linhas/colunas alternadas em percentual
  do passo da grade, incluindo espacamento. 50 = meia celula; padrao 0,
  intervalo -1000..1000. Nao desloca todas as copias como shift_x/shift_y.
- alternate_vertical: false desloca linhas horizontalmente; true desloca
  colunas verticalmente. Escolha estatica Horizontal/Vertical na gaveta.

Os dez parametros numericos aceitam keyframes, curvas, undo/redo e controles
procedurais pelo contrato publico existente. Espelhamento e direcao nao sao animaveis.
A transparência e preservada. A ordem na pilha de efeitos e respeitada.
Para identidade: tile/output X/Y em 100, espacamentos/deslocamentos/angulo em zero,
mirror desligado.
Area de saida zero em qualquer eixo gera transparencia.

### Renderizacao

Uma passagem GLES calcula a celula e faz uma amostragem da imagem de entrada.
O numero de copias nao adiciona passagens nem duplica objetos na CPU.
Regioes de entrada conservam os pixels necessarios, inclusive fora do viewport;
windows nao podem recortar uma fonte que a repeticao vai voltar a amostrar.
As regras existentes de admissao de memoria/GPU continuam aplicaveis; dimensoes
grandes e outros efeitos ainda podem ter custo. Nao ha promessa de FPS constante.
Preview, miniatura e exportacao compartilham o mesmo grafo/shader.

### Roteiro curto

1. Aplique a uma forma pequena: com area 300%, devem aparecer copias ao redor.
2. Reduza tamanho X/Y a 50%: devem caber mais copias na mesma area.
3. Anime deslocamento X de 0 a 100: o padrao deve completar um periodo quando
   tamanho X for 100%, sem espelhamento.
4. Ative espelhamento: copias vizinhas devem inverter a imagem; o periodo
   completo passa a duas celulas nesse eixo.

## Apendice: deformacao por pinos e Curvar

### Contrato

- `motionlayer.bend`, schema 1: angle -180..180 graus, start 0..100%,
  tune_head/tune_tail 0..100%. Defaults: 0, 0, 75, 25.
- origin: 0 esquerda, 1 direita, 2 topo, 3 base (default).
- before: 0 fixo, 1 mesmo lado, 2 lado oposto.
- `motionlayer.puppet`, schema 1: ate 16 slots estaveis, numerados de 0 a 15.
- Por slot: pinN_active Boolean; pinN_rest Vector2 em percentual local;
  pinN_x/pinN_y Float em percentual local, com canais/keyframes nativos.
- Origem dos percentuais: canto superior esquerdo dos limites originais do elemento.
  Rest pertence ao estado de montagem; deslocar um pino altera o destino, nao o rest.
- Destinos permitem -500..500%; a montagem permanece dentro de 0..100%.
- Excluir um pino nao renumera os outros nem muda seus property paths.
- Estado neutro nao cria um passe adicional: zero graus ou nenhum pino deslocado.

### Renderizacao

Curvar usa arco circular em coordenadas locais. Os ajustes de extremidade limitam
continuamente a curvatura perto dos extremos do inicio. O contrato visual e inspirado
no guia do Alight, mas sua formula proprietaria nao e publicada: nao se promete
equivalencia numerica de presets.

Puppet usa rigid moving least squares: centros ponderados e rotacao local de melhor
ajuste. Nao e um deslocamento radial de pixels. As distancias usam a proporcao real
da fonte, nao um quadrado UV que deformaria imagens retangulares.

Ambos rasterizam uma malha direta 64 x 64 em GLES. Vertices sao deformados antes
da transformacao para o destino; o mesmo frame graph atende preview/export/capas.
Transparencia usa cores premultiplicadas, amostragem externa transparente e blend
por triangulo. Shaders novos sao compilados sob demanda, nao no aquecimento geral
da abertura de projetos.

O recorte de uma janela nao pode eliminar a fonte que sera transportada para dentro
da area visivel. Planejamento de regioes e dependencia de Motion Blur preservam
a fonte inteira exigida pela deformacao.

### Escopo Honesto

- Este Puppet deforma uma superficie texturizada; nao replica o Advanced Puppet
  completo da Adobe. Nao existem ainda pinos de rigidez, sobreposicao, escala ou
  rotacao individual, nem malha adaptativa gerada pela silhueta alpha.
- Uma camada plana em 3D continua pertencendo ao seu plano/camera e ao depth
  compositing. Isto nao transforma um pino XY em um controle de profundidade Z.
- Extrusao geometrica e superficies laterais exigem um contrato proprio; uma
  deformacao de textura nao deve ser anunciada como curvatura de um solido 3D.
  Nestes volumes, os novos efeitos nao deformam a geometria e a edicao de pinos
  fica indisponivel. Outros efeitos existentes continuam no fluxo original.
- O efeito nao propaga deformacao para filhos de parenting nesta primeira versao.
  Parenting tradicional continua funcionando sem nova heranca implicita.
- Malha fixa e uma aproximacao espacial. Dobras extremas podem sobrepor triangulos;
  nao existe ordenacao artistica de sobreposicao por pinos nesta versao.
- Limites de memoria/texture size da GPU continuam reais. Nao reduzir fidelidade
  silenciosamente nem prometer ausencia de custo sem medir.

### Referencias

- Adobe, Animating with Puppet tools:
  https://helpx.adobe.com/after-effects/desktop/animate-in-after-effects/animate-with-puppet-tools/animating-puppet-tools.html
- Alight Motion, Bend: https://guide.alightmotion.com/effects/bend
- Schaefer, McPhail, Warren, Image Deformation Using Moving Least Squares (2006):
  https://people.engr.tamu.edu/schaefer/research/mls.pdf

### Preenchimento, Granulacao/Ruido, Varreduras de luz e Vinheta

Efeitos nativos, acessiveis com os mesmos setters publicos e permissao
`layers.modify`. `effect.add` aceita o nome curto ou com prefixo
`motionlayer.`. Nao precisam de runtime ou plugin privado.

| Tipo | Parametros Float |
| --- | --- |
| `fill` | `opacity` (100, 0-100%), `mix` (0, 0-100%) |
| `noise_grain` | `noise_mode` (0 Grain / 1 Noise), `amount` (15, 0-100%), `grain_size` (2, 1-128 px), `roughness` (50, 0-100%), `monochrome` (1, 0/1), `seed` (0, 0-65535), `evolution` (0, -100000..100000), `speed` (24, 0-120 mudancas/s), `freeze` (0, 0/1) |
| `light_sweep` | `center_x/y` (50, -500..500%), `angle` (30, -3600..3600 graus), `width` (40, 0-10000 px), `intensity` (100, 0-1000%), `profile` (1, 0-2), `reception` (0, 0-2), `edge_intensity` (50, 0-1000%), `edge_thickness` (2, 0-128 px) |
| `linear_light_sweep` | mesmos controles de `light_sweep`, exceto `edge_intensity` e `edge_thickness` |
| `vignette` | `amount` (60, 0-100%), `center_x/y` (50, -500..500%), `radius` (75, 0-300%), `roundness` (100, 1-200%), `softness` (50, 0-100%) |

Todos, exceto `noise_grain`, tem `color` (Color), branco por padrao,
ou preto para Vinheta. Usar `setColorParam` / `setColorKeyframe` para cores.
Todos os campos Float aceitam keyframes e curvas; switches tambem sao Float,
nao Boolean. `noise_mode`, `monochrome`, `freeze`, `profile` e `reception`
mudam discretamente. `profile`: 0 Linear, 1 Smooth, 2 Sharp.
`reception`: 0 Add, 1 Composite, 2 Cutout.

Fill respeita alpha; opacity controla sua transparencia, e mix mistura o
original. Granulacao/Ruido nao altera alpha. Evolucao automatica e
deterministica no tempo: `floor(timeMs*speed/1000)`; `freeze=1` desliga
o relogio automatico, mas nao impede animar `evolution` manualmente.
Speed animado mapeia fase absoluta, nao integra historico do scrub.

As varreduras iluminam uma faixa, nao sao `linear_wipe`. Light Sweep tambem
ilumina bordas do alpha e seu brilho diminui a partir do centro do feixe.
Linear Light Sweep tem intensidade uniforme no interior; `profile` afeta
somente suas bordas (rampa linear, suave ou nitida), sem iluminar o alpha.
Os canais existentes continuam compativeis. Vinheta colore uma elipse
nas bordas sem criar pixels opacos fora da imagem.
GLES implementado; preview/export usam o mesmo pipeline e Vulkan recua
para o backend compativel existente. Um passe por efeito; nao e garantia
de FPS em todo projeto nem replica pixel-exata de um plugin proprietario.

### Qualidade, custo e edicao de pinos

Em planos 3D, a densidade da textura considera a projecao do elemento e as
transformacoes/camera; o teto e a dimensao maxima de textura aceita pela GPU.
O mesmo planejamento e usado no preview e na exportacao. Deslocamentos extremos
e padding conservador ainda podem limitar a qualidade dentro desse teto.

A malha espacial tem 65 x 65 vertices. Destinos de pinos animados reaproveitam
os coeficientes de montagem; mudar a montagem exige recalculo. Muitos pinos
movendo-se e pilhas de efeitos complexas continuam tendo custo: os limites
da API nao sao uma garantia de desempenho nem uma promessa de FPS.

O editor dedicado reaproveita o touchpad: arrastar posiciona o cursor e tocar
adiciona um pino. Pinos existentes podem ser selecionados e movidos. A edicao
numerica XY permanece disponivel. No touchpad, criar/remover keyframe opera
os dois eixos atomicamente e conserva curvas existentes; controles numericos
continuam editando cada eixo separadamente.
