Imported from sandeco/mira-animator (
agents/mira-tactics/SKILL.md). Install upstream withnpx skills add sandeco/mira-animator --skill mira-tactics. Copyright stays with the author.
Skill: Mesa tática (campo com jogadores, jogadas gravadas e replay)
Um slide dedicado: campo cinematográfico com os dois times na formação real, cada jogador um boneco chibi animado (ou disco clássico) na cor do time (número acima da cabeça, nome ao clicar). O apresentador arrasta, adiciona, apaga e desenha setas/zonas/traços ao vivo, grava jogadas por quadros-chave e as reproduz com interpolação suave, salvando cada uma como JSON.
Fonte da verdade: o motor oficial é embarcado no pacote e chega com a instalação. Procure-o nesta ordem e use o primeiro que existir:
mira-templates/decks/mesa-tatica/index.html(projeto com Mira instalado)templates/decks/mesa-tatica/index.html(repositório fonte do Mira)node_modules/mira-animator/templates/decks/mesa-tatica/index.htmlSe nenhum existir, a instalação está desatualizada: rode
npx mira-animator update(ouinstall) e ele aparece. NUNCA reconstrua o motor do zero. Gere sempre a partir dele: copie o arquivo e reescreva apenas o blocovar TACTICS = { ... }no topo do<script>. Todo o resto (campo, peças, edição, jogadas, vertical) já vive no motor. O HTML é único e autocontido: sem CDN, sem D3, abre porfile://.A especificação completa do motor está em
_reversa_sdd/manipulação/mesa-tatica.md(repo fonte). As decisões de produto listadas lá e resumidas no fim desta skill não podem regredir.
Modelo mental
O Mira não simula a partida. Ele monta um slide declarativo: a CONFIG (TACTICS) descreve orientação, título e os dois times (cores, formação e jogadores); o motor desenha o campo e posiciona tudo.
- Coordenadas de campo, não de tela. Tudo usa
u0..1 no comprimento (gol esquerdo ao direito) ev0..1 na largura. Virar para a vertical é só remapear (u,v); nada é recalculado. - Número sempre visível, nome sob clique. O número flutua acima da cabeça do boneco; o nome aparece num balão ao clicar (a peça selecionada escurece).
- Goleiro sempre presente em cada time, com uniforme próprio (
goalie), o mais fiel possível ao uniforme real, posicionado pela formação (ver diretiva abaixo). - Campo abre limpo: sem setas nem zonas pré-desenhadas (
arrows: [],shapes: []).
Visual (não simplificar)
Gramado escuro com faixas de corte em xadrez, vinheta nas bordas, moldura azul iluminada estilo mesa e varredura de luz em loop perpétuo (Regra Zero atendida no cenário). Setas com glow dourado e um filete de luz correndo dentro (loop infinito); zonas com brilho na cor escolhida. O título vive dentro do SVG, em caixa alta com o "x" minúsculo ("BRASIL x NORUEGA"), alinhado à linha lateral ESQUERDA do campo; o crédito "MIRA Animator by sandeco" fica discreto na mesma linha de base, alinhado à lateral direita. eyebrow oculto por padrão (só se o usuário pedir).
Peças (jogadores)
Dois estilos, alternáveis em runtime pelo botão ●/⛹ da barra:
- boneco (padrão): miniatura chibi procedural em SVG: cabeça com cabelo, camisa na cor do time SEM número (borda da camisa na cor do calção), calção, bracinhos e pernas que TROTAM no lugar em loop perpétuo, cada jogador com fase e ritmo próprios. Número em branco com contorno escuro flutuando acima da cabeça.
- botao: disco clássico na cor do time com o número no centro.
Comum aos dois: sombra e halo pulsante na cor do time, goleiro com cor própria, seleção escurece e mostra o nome, entrada com pop escalonado.
Aparência real: cada player aceita skin e hair para refletir a aparência pública do atleta. Tons nomeados: skin: 'clara' | 'media' | 'escura', hair: 'preto' | 'castanho' | 'loiro' | 'ruivo' | 'grisalho' (hex direto também vale). Sem os campos, o motor usa variação determinística. Semântica calibrada: jogador negro = 'escura'; pardo = 'media'; 'media' NÃO serve para quem é negro (fica caramelo e não reflete a realidade); em dúvida entre os dois para um atleta negro, use 'escura'.
Fluxo conversacional
Regra: sempre o ÚLTIMO JOGO, validando os 4 atributos
Quando o usuário cita times reais, o Mira sempre pesquisa o último jogo disputado de cada time citado (ou o jogo do dia) e valida, para cada titular que COMEÇOU o jogo, 4 atributos obrigatórios, nunca deduzidos de memória:
- Nome: o onze que de fato começou, não "escalação provável" nem time-base. Se um titular habitual não começou (lesão, rodízio, suspensão), entra quem começou de fato.
- Número: da numeração oficial do torneio (ex.: lista da CBF). Sem fonte confiável, aproxime e MARQUE como não confirmado no reporte.
- Posição: o slot real na formação (ponta esquerda x direita, lateral, volante, centroavante), confirmado na fonte; a ordem do array
playerscodifica isso. (Ex. real: Vini Jr. é ponta ESQUERDA, nunca direita.)- Aparência pública:
skinehairrefletindo a aparência real e amplamente conhecida do atleta. Método validado: confirmar por FOTO, baixando a imagem oficial de cada jogador (API da Wikipédia:action=query&prop=pageimages&pithumbsize=330, título URL-encoded) e OLHANDO a imagem, cruzando com mídia especializada quando houver. Memória não vale como fonte (já errou Gabriel Magalhães, Casemiro, Cunha, Pedersen, Heggem, Wolfe e Nusa). Em dúvida, use o tom mais provável e marque como aproximação.Fonte preferencial: sites locais/oficiais de cada time (federação ou imprensa local do país; ex.: ge.globo/CBF para o Brasil), cruzando com uma segunda fonte. Busque também a cor do uniforme de goleiro e a cor do calção (
shorts). O reporte final cita as fontes e lista explicitamente o que ficou como aproximação. Um jogador sem qualquer um dos 4 atributos é um erro de geração, não uma opção.
Quando o pedido não tiver times reais ("um time azul e um vermelho, 11 contra 11"), pule a pesquisa: gere direto com as cores pedidas e números sequenciais (name vazio; o usuário renomeia no slide com duplo clique).
Schema da CONFIG (TACTICS)
Reescreva apenas este objeto no topo do <script>:
var TACTICS = {
sport: 'futebol',
orientation: 'horizontal', // ou 'vertical' (tecla V vira em runtime; ?vertical=1 na URL)
eyebrow: '', // oculto se vazio (padrão)
title: 'Brasil x Noruega', // no SVG, caixa alta com "x" minúsculo
teams: [{
name: 'Brasil',
color: '#FFDF00', // cor da camisa
shorts: '#1D50C8', // calção e borda da camisa
goalie: '#5B5F63', // uniforme do goleiro (o mais real possível)
formation: '4-3-3', // 4-3-3 | 4-4-2 | 3-5-2 | 4-2-3-1 (11 jogadores); fora disso, grade
side: 'left', // 'left' | 'right'
players: [
{ num: '1', name: 'Alisson', gk: true, skin: 'clara', hair: 'ruivo' }
// ... um por jogador; a ORDEM casa com os slots da formação
]
}, { /* time 2 */ }],
arrows: [], // padrão: vazio (campo abre limpo)
shapes: [] // padrão: vazio
};
Regras: o goleiro tem gk: true e vem primeiro; a ordem dos players casa com as posições da formação (goleiro, defesa, meio, ataque); side separa as metades; cores em hex (se as camisas forem parecidas, escolha um tom distinto para um deles e avise). O motor desenha campo de futebol; times com nº de jogadores diferente de 11 (ou formação desconhecida) são posicionados em grade.
DIRETIVA: a base começa SEMPRE com os dois times completos em formação
O
TACTICSinicial traz os dois times completos (11 x 11, ou o elenco cheio combinado) na formação real, posicionados pelo motor — semu,vabsolutos no config. Gol, jogada ou lance é sempre um quadro gravado (data/*.json) por cima dessa formação cheia, nunca um elenco reduzido com posições fixas como estado inicial; a animação (fluida no telão e no remote) reconstrói o lance a partir daí. Para congelar um momento, navegue até o quadro; não corte o elenco nem coleu,vabsolutos noTACTICS.
DIRETIVA: todo time SEMPRE tem goleiro
Nenhuma mesa sai sem os dois goleiros, com
gk: truee uniforme próprio (goalie). Como a base começa 11 x 11 na formação, o goleiro já entra no gol pela própria formação. Um time sem goleiro é erro de geração.Cuidado com jogadas salvas ao alterar o elenco: os
idsão sequenciais na ordem do array (time 1 e depois time 2), então inserir/remover jogador desloca osidseguintes e desalinha os.jsondedata/. Prefira acrescentar o goleiro no fim do array do time e, se algum id posterior mudar, remapeie as chaves dos frames nos.jsonafetados (jogadores ausentes de um frame ficam parados na posição da CONFIG, então o goleiro fica no gol durante a jogada).
Barra de ferramentas (janela flutuante)
Só ícones com tooltip, sem rótulos de texto, zoom: 1.21, arrastável pela alça ⠿. SEM botão de ocultar (decisão explícita: usuário se trancava para fora); ocultar/mostrar é só pela tecla F. No modo vertical a barra cola no rodapé do slide e re-centraliza.
Ferramentas: ✥ mover/selecionar (arrasto no vazio = seleção em caixa; grupo move junto), adicionar jogador com um MINI BONECO nas cores de cada time, ➜ seta, ✎ paint (desenho livre com glow, tecla P), ▭/◯ zonas, ✕ apagar setas/zonas/traços, 🗑 apagar jogador (clique no jogador), 💾 salvar tudo da mesa no navegador, paleta de 6 cores (setas/zonas/paint), ●/⛹ estilo das peças, ⟳ virar, ↺ desfazer, ⌧ limpar (setas+zonas+traços), 🎬 abre/fecha o painel de jogada (mesmo que a tecla R; essencial no celular/touch), ? help (painel "COMANDOS" com fechar estilo macOS sempre visível; Esc fecha).
Gravação de jogada (quadros-chave, tecla R)
Janela flutuante própria "JOGADA" (arrastável, aberta/fechada por R ou pelo botão 🎬 da barra — o botão é a única via no celular/touch), que nasce dentro do campo, canto inferior esquerdo. No primeiro R sem quadros, a cena atual vira o quadro 1 automaticamente.
- ⏺ Quadro captura um snapshot (jogadores + bola); contador
atual/total. - ▶ Play / ⏸ (barra de espaço) interpola entre quadros (1,2s por trecho) numa linha do tempo única da jogada: ease só no começo e no fim, movimento linear entre os quadros intermediários (flui sem parar em cada quadro; nunca aplicar ease por trecho, que dá "pula-pula"). O trote dos bonecos roda por cima; tocar no campo pausa.
- ✓ ATUALIZA o quadro atual com as posições da tela; ⌫ apaga só o quadro atual; ⏮ ◀ ▶| navegam; ✕ apaga todos. Ordem dos botões (UX, não mudar): ⏺ · contador · ✓ · ⏮ ◀ ▶ Play ▶| · ⌫ ✕ · | · 💾 ▤ (destrutivos juntos no fim, ⌫ longe do ⏮).
Persistência: um JSON por jogada em data/
As jogadas são ARQUIVOS .json na pasta data/ do deck (decks/<nome>/data/<slug>.json), no schema mira-tactics-play/1:
{
"schema": "mira-tactics-play/1",
"name": "contra-ataque pela esquerda",
"title": "Brasil x Noruega",
"frames": [
{ "players": { "1": [0.045, 0.5] }, "ball": [0.5, 0.5] }
]
}
Ids de jogador são sequenciais na ordem da config (time 1 depois time 2); coordenadas u,v normalizadas 0..1. Escrita/leitura via File System Access API do Chrome: ao apertar R, a lista de jogadas carrega automaticamente da pasta data/ (na primeira vez o picker abre sozinho e o usuário confirma; conectada, vale a sessão inteira). Jogada carregada (▶) fica com o nome pré-preenchido, então 💾 sobrescreve o mesmo arquivo; cada linha da lista tem 💾 próprio. Fallback sem a API: 💾 baixa o .json e ⇪ importa um avulso. Esse JSON é também o contrato de saída do futuro pipeline vídeo→jogada (YOLO; ver dev/brainstormings/BRAINSTORM_VIDEO_PARA_JOGADA.md): não quebrar o schema. Um exemplo embarcado vive em mira-templates/decks/mesa-tatica/data/vini-correndo.json.
Vertical (/mira-vertical)
Não aplique o reflow genérico da skill mira-vertical: aqui o campo apenas gira 90 graus. Três formas: orientation: 'vertical' na CONFIG, ?vertical=1 na URL, ou tecla V ao vivo. A formação é preservada; não crie arquivo novo só para virar.
Atalhos
| Tecla | Ação |
|---|---|
| F | mostra/oculta a barra de ferramentas |
| P | liga/desliga o paint |
| R | abre/fecha o painel de jogada (1ª vez grava o quadro 1) |
| V | vira o campo |
| espaço | play/pausa da jogada |
| Ctrl+Z | desfazer |
| Delete | apaga selecionados |
| Esc | fecha help/lista ou limpa seleção |
| duplo clique | renomeia o jogador (nº e nome); Enter/clique fora salva, Esc cancela |
Passos
- Interpretar o pedido: times, cores, nº de jogadores, orientação.
- Pesquisar o último jogo de cada time (via
WebSearch+WebFetch) e validar os 4 atributos de cada titular (nome, número, posição, aparência por FOTO), mais cores de camisa, calção e goleiro. Só pule se não houver times reais. - Copiar o motor de referência (ver "Fonte da verdade") para
decks/<nome>/index.htmle criar a pastadecks/<nome>/data/(vazia) para as jogadas. - Reescrever o
var TACTICS = { ... }com os dados: os dois times completos (11 x 11) na formação, semu,vabsolutos. Não toque no motor abaixo. - Conferir: os dois times completos (11 x 11) posicionados pela formação (sem
u,vno config), 4 atributos em todos, goleiro com cor própria, cores distintas, campo abrindo limpo. - Se for vertical:
orientation: 'vertical'(ou instruir?vertical=1/ teclaV). - Reportar: caminho do arquivo, atalhos (F, P, R, V, espaço, Ctrl+Z), as fontes usadas e o que ficou como aproximação.
Decisões de produto (não regredir)
- Escalações reais validando os 4 atributos obrigatórios por jogador, com aproximações marcadas no reporte.
- Campo abre limpo: sem setas/zonas pré-desenhadas.
- Barra sem rótulos de texto; times como mini bonecos.
- Sem botão de ocultar painéis; teclas F/R são a única via.
- Número acima da cabeça, camisa limpa (borda na cor do calção).
- Painel de jogada nasce dentro do campo, embaixo à esquerda.
- Título e crédito dentro do SVG, alinhados às linhas laterais.
- Aparência confirmada por FOTO; negro = 'escura', pardo = 'media'.
- R carrega a lista de jogadas de
data/automaticamente; primeiro R grava o quadro 1. - Botões destrutivos do painel de jogada (⌫ ✕) agrupados, longe do ⏮.
- Jogada salva é atualizável (nome pré-preenchido + 💾 por linha da lista).
- Não quebrar o schema
mira-tactics-play/1(jogadas já salvas do usuário). - Todo time sempre tem goleiro (
gk: true), posicionado pela formação (base sempre completa; ver decisão 23). - Renomear jogador (nº e nome) por duplo clique é robusto ao arrasto (detectado no
pointerup, não só nodblclicknativo) e a área clicável cobre o número flutuante; Enter e clicar fora salvam, Esc cancela. - O motor expõe
window.miraTactics(getState/setState/onchange) para o mira-remote espelhar peças, bola, setas, zonas e desenhos entre notebook e celular. Emfile://(sem shell)onchangefica null e nada trafega (regressão zero). Não remover. - O painel de jogada abre também pelo botão 🎬 da barra (não só pela tecla
R), para funcionar no celular/touch; e o estado da gravação (quadros, índice e painel aberto) entra no sync dowindow.miraTactics, então gravar/abrir num aparelho reflete no outro. - Bola "colada" ao portador: mover um jogador encostado na bola arrasta a bola junto (condução), até o usuário mover a bola para longe (desencosta). Limiar de contato ~32px no espaço do tabuleiro.
- Reprodução da jogada numa linha do tempo ÚNICA (ease só no começo/fim, linear entre os quadros): flui sem parar em cada quadro. Nunca voltar ao ease por trecho.
- Botão 💾 na barra salva TUDO da mesa no navegador (localStorage por deck): elenco (nº/nome/aparência), posições, bola, setas, zonas, desenhos e a jogada gravada; ao recarregar, restaura (se o elenco-base do TACTICS não mudou). É o único jeito de renomear/editar e não perder no reload.
- Duas borrachas distintas: ✕ apaga setas/zonas/traços; 🗑 apaga jogador. Não voltar a apagar jogador com o ✕.
- Ao salvar uma jogada em
data/, o.jsongrava também umroster(id → nº/nome/aparência) e o load aplica; o schema seguemira-tactics-play/1(campo novo é opcional, jogadas antigas semrostercontinuam carregando). - Reprodução (▶ Play) NÃO é sincronizada por stream de posições no remote (senão engasga o telão). Durante o Play,
getRemoteStateCONGELA o que muda a cada frame (posições erec.next) e emite só UM comandoplay: { token, playing, from }; cada aparelho roda a PRÓPRIAplayReclocal a partir deplay.from(fluido). O emissor incrementatokennostartPlay(Play do usuário); o espelho ADOTA o token recebido emsetRemoteStatee chamaplayRecsem gerar outro (sem bounce). Enquantorec.playing,setRemoteStateretorna cedo (não deixa o sync cortar a animação local). Isso mantém o PC 100% fluido, pois some o trabalho por frame que competia com o rAF. Nunca voltar a transmitir posições por frame durante o Play. - A base começa SEMPRE com os dois times completos (11 x 11) na formação definida, sem
u,vabsolutos no config. Gol/jogada/lance vive emdata/*.json(quadros gravados) por cima da formação cheia, nunca um elenco reduzido com posições fixas como estado inicial. Para mostrar um momento, navegue até o quadro; não corte o elenco.
Checklist
- Slide gerado a partir do motor embarcado (só o
TACTICSmudou; motor intacto). - Pesquisou o ÚLTIMO JOGO de cada time e validou os 4 atributos: nome, número, posição e aparência (por foto), em sites locais/oficiais, cruzando fontes.
- Citou fontes e marcou aproximações (número sem fonte, cor de goleiro, skin/hair em dúvida).
-
shortsegoaliepreenchidos, fiéis aos uniformes reais. - Os dois times completos (11 x 11) na formação definida, sem
u,vabsolutos no config; gol/lance (se houver) mora emdata/*.json. - Os dois goleiros presentes (
gk: true), posicionados pela formação. - Campo abre limpo (
arrows: [],shapes: []). - Pasta
data/criada no deck novo. - Título em caixa alta com "x" minúsculo, alinhado à lateral esquerda;
eyebrowoculto salvo pedido. - Cores dos dois times distintas e fiéis.
- Vertical vira o campo (orientation/
?vertical=1/teclaV) sem quebrar a formação. - Preserva a Regra Zero do Mira (loops perpétuos: varredura de luz, trote dos bonecos, halo).
- Texto em português correto, sem travessão.