Pular para o conteúdo
SheetForge

Capacidades e Limites

Esta página lista tudo que o SheetForge não faz, ainda não consegue fazer, ou faz de forma diferente do que você poderia esperar, com o motivo, a solução alternativa, e se há espaço futuro para isso.

Formato por item: O quê / Por quê / Solução alternativa (+ espaço futuro, quando relevante).


1. Plataforma e dependências

O Addressables é obrigatório — o pipeline trava sem ele

  • O quê: o carregamento por endereço é o caminho de runtime, e o tipo AssetRef@Group precisa do com.unity.addressables, então o pacote precisa estar instalado para usar o asset. O próprio asset compila sem ele — todo código que usa o Addressables fica protegido atrás de uma version-define SHEETFORGE_ADDRESSABLES que só é ativada quando o pacote está presente.
  • Comportamento quando ausente: o pipeline inteiro (importação · exportação · push · gravação de volta da autoria) fica travado, não degradado — executar qualquer ponto de entrada mostra um aviso de instalação e para. Não existe um caminho parcial ou silencioso (nenhum fallback do tipo "pular validação de chave de asset"). Como o Editor compila sem o pacote, ele nunca cai em Safe Mode: a janela Primeiros Passos abre normalmente, e sua linha do Addressables mostra ✗ com um botão Open Package Manager. O bootstrap sem dependências SheetForge.Setup permanece como uma rede de orientação para outras falhas de compilação, não relacionadas.
  • No Data Studio, sem o pacote: o botão seletor ⊙ da célula de asset e o seu alvo de arrastar e soltar ficam desabilitados, com o motivo como tooltip; digitar um endereço na célula continua funcionando.
  • Por que não uma abstração dupla Resources/Addressables: deliberadamente não construída — julgada engenharia excessiva.
  • Solução alternativa: instale o Addressables. O aviso de importação da Asset Store cuida disso antes da compilação; se você pulou isso, o Editor, que continua compilando, orienta você a instalar.
  • Verificação: os dois ramos são exercitados ao vivo — com as version-defines removidas (simulando "Addressables ausente"), os assemblies do produto e de teste compilam com 0 erros; restauradas, 0 erros e 0 avisos. Verificado de forma independente, de ponta a ponta, importando o asset em um projeto novo sem o Addressables instalado: o projeto compila e a janela de orientação de instalação aparece conforme projetado.

O Unity Localization é opcional — apenas a sincronização de StringTable espera por ele

  • O quê: planilhas de localização (@loc), referências LocRef, constantes de chave, relatórios de cobertura, Exportação, Push, xlsx e o aplicativo web funcionam sem o com.unity.localization instalado. A única coisa que espera é a saída de sincronização de StringTable: ela mostra um aviso de instalação (uma vez por sessão) e para. Todo código que toca o pacote fica atrás de uma version-define SHEETFORGE_LOCALIZATION, então todo assembly e toda linha de código gerado compila sem o pacote — os campos gerados são a struct LocRef simples, nunca um tipo do pacote.
  • Comportamento quando ausente: nada degrada e nada é pulado silenciosamente em outro lugar — as planilhas continuam sendo planilhas completas; apenas a saída de sincronização fica travada, com o motivo exibido.
  • Versão suportada: 1.5 ou mais recente.
  • Solução alternativa: instale o pacote quando quiser as tabelas; tudo o que foi autorado antes desse momento sincroniza na próxima importação concluída. Veja Planilhas de Localização.

Nenhuma instalação programática em um clique

  • O quê: a janela de rede de segurança apenas orienta você; ela não instala o pacote sozinha. A mesma regra cobre o aviso de instalação do Unity Localization.
  • Por quê: as regras de submissão da Asset Store restringem a modificação programática de pacotes; uma janela de orientação é a escolha segura e compatível.

O define de detecção de produto SHEETFORGE não é removido automaticamente

  • O quê: o assembly Editor se autorregistra com um símbolo de scripting define SHEETFORGE em todo build target, para que outros assets possam detectar que o SheetForge está instalado em tempo de compilação (veja Autoria de Plugins ▸ Detectando o SheetForge a partir de outro asset). O registro é idempotente (adicionado apenas quando ausente — sem recompilações repetidas depois que já está presente).
  • Limite: se você excluir o asset depois, esse define permanece — o código que perceberia a remoção vai embora junto com ele.
  • Solução alternativa: remova-o manualmente em Project Settings ▸ Player ▸ Scripting Define Symbols (por plataforma). Deliberadamente não mantemos um observador em segundo plano rodando só para limpar um símbolo. Isso é diferente de SHEETFORGE_ADDRESSABLES, um version-define interno que apenas reflete se o pacote Addressables está presente.

2. Origem Planilha do Google

O modo ExportUrl é somente leitura

  • O quê: Push, reflect, edição de estrutura e exclusão ficam todos desabilitados no modo ExportUrl.
  • Por quê: é o caminho de exportação sem autenticação, compartilhado por link — somente leitura por natureza. O Push sempre exige credenciais do SheetsApi, verificadas antes de qualquer chamada de rede.
  • Solução alternativa: use o modo SheetsApi (conta de serviço — veja Configuração da Planilha do Google) para qualquer gravação de volta.

O ExportUrl exige um mapa de gid

  • O quê: um mapa de gid vazio faz a importação falhar; gids duplicados são rejeitados.
  • Por quê: uma URL de exportação sem gid retorna silenciosamente apenas a primeira aba — uma armadilha de corrupção silenciosa, então a importação a rejeita. O SheetsApi descobre as abas automaticamente.
  • Solução alternativa: registre o valor #gid= de cada aba, ou use o SheetsApi.

O Push exclui linhas por chave, e somente as verificadas

  • O quê: um registro excluído localmente é removido da planilha ao vivo no push — depois que a busca anterior ao envio confirma que sua chave ainda está na linha que sua importação viu. Uma linha que já sumiu conta como concluída (Push idempotente ao reenviar); uma chave encontrada em uma linha diferente é ignorada com um aviso, nunca excluída por posição. As exclusões são listadas em sua própria seção do resumo de aprovação e enviadas por último, de baixo para cima por aba.
  • Por quê: comparar por chave com a planilha ao vivo é o que torna a exclusão segura em uma planilha que pode ter se alterado; qualquer coisa que a comparação não consiga confirmar é deixada intocada.
  • Limite: uma origem sem a capacidade de excluir linhas (um provedor personalizado que nunca a adquiriu) recorre ao comportamento antigo — a exclusão é relatada e a linha ao vivo é deixada para você remover.

O Push ignora células em conflito (por design)

  • O quê: células editadas por terceiros desde a sua importação, linhas cuja chave se moveu de forma ambígua, linhas ausentes, ou chaves duplicadas ao vivo são ignoradas com avisos — não sobrescritas.
  • Por quê: é a rede de segurança funcionando: as células enviadas são válidas; as omissões protegem as alterações de outras pessoas e evitam gravações na linha errada.
  • Solução alternativa: verifique as contagens de aplicadas/ignoradas no relatório; reimporte para reconciliar, depois faça o Push novamente. (Uma UI de resolução de conflitos seria uma funcionalidade separada — não está planejada.)

O Push exige uma coluna-chave

  • O quê: uma aba alterada sem uma coluna-chave RecordId não pode ser enviada por Push — é um erro de plano que bloqueia o Push inteiro (zero envios para toda aba; nenhum envio parcial).
  • Por quê: o Push relocaliza linhas pela chave na planilha ao vivo; sem uma chave, a proteção contra linha errada não se sustenta.
  • Solução alternativa: adicione uma coluna-chave, ou exporte para um arquivo e cole.

3. Origem xlsx

  • A renomeação de aba exclui abas de origem xlsx — proteção de workbook multi-planilha. Renomeie no workbook, reimporte.

  • A propagação de renomeação de chave para uma aba de origem xlsx bloqueia o lote inteiro — o caminho xlsx não consegue fazer atualizações cirúrgicas de célula com segurança, e a reflexão parcial nunca é permitida. Edite essa aba diretamente, reimporte.

  • Células irrepresentáveis são rejeitadas — células de fórmula sem valores em cache, células de erro, e tabulações/quebras de linha dentro de uma célula. O leitor OOXML embutido é intencionalmente minimalista (zero código de terceiros). Materialize fórmulas; use ; para listas.

  • Apenas valores — fórmulas, datas e formatação são interpretadas, honestamente — uma célula de fórmula contribui com o seu valor em cache (nunca recalculado), uma célula com formato de data é lida como texto de exibição yyyy-MM-dd, e formatos numéricos, células mescladas e gráficos não são importados. O diálogo de importação do aplicativo web nomeia o que de fato aconteceu em uma nota "Como esta pasta de trabalho foi lida"; no editor a mesma política se aplica silenciosamente por célula (as rejeições acima continuam relatando por célula).

  • Algumas regras de dropdown exportadas não podem ser carregadas — a exportação é um único workbook, então o dropdown de uma coluna de referência é gravado como um intervalo real sobre a coluna-chave da planilha de destino, o mesmo significado que a regra do Google tem. Três casos ainda são deixados de fora, e nomeados juntos em um único aviso DropdownNotSupportedByFormat:

    • um membro de lista contendo uma vírgula (o separador embutido o dividiria);
    • uma lista embutida acima do limite de 255 caracteres do formato (aspas incluídas);
    • um intervalo cuja aba de destino não está no workbook.

    Os valores são exportados por completo de qualquer forma. Veja Fontes, Exportação e Envio.

4. Autoria — o Data Studio

Uma aba sem coluna-chave não ganha novos registros, e suas edições de valor perdem a âncora

  • O quê: uma aba sem uma coluna-chave RecordId importa e é exibida normalmente, e a edição de estrutura funciona por completo — adicionar, remover, renomear, reordenar colunas e marcadores, além da renomeação e exclusão em nível de planilha. O que ela não consegue ganhar é um novo registro, já que um registro sem chave não pode ser nomeado nem referenciado:

    • o controle de adicionar linha fica desabilitado;
    • o seletor de referência recusa criar ali ("… não tem coluna-chave, então um novo registro não pode ser criado ali");
    • uma ação de canvas ou de inspector que tenta gravar nela não faz nada.

    As células de valor são editáveis — mas, sem chave para endereçar a linha, a edição é preparada apenas contra a posição da linha.

  • Por quê: toda edição preparada é normalmente endereçada de forma lógica, como (tab, record key, field), e reresolvida contra a planilha pouco antes de ser gravada. É isso que permite que uma edição sobreviva a uma reimportação, a uma reordenação de linha, ou a alguém inserindo linhas acima dela. Sem coluna-chave, não existe esse endereço, então a edição passa presa a um número de linha em vez disso — fora dessa rede de segurança.

    Então, se as linhas da planilha se moverem debaixo de você antes de você gravar de volta (uma reimportação, ou alguém editando a origem diretamente), uma edição ancorada por posição pode cair na linha errada. Prepare e reflita essas edições em lotes curtos.

  • Solução alternativa: adicione uma coluna RecordId (a edição de estrutura está disponível, então você pode fazer isso na mesma janela), reflita, e a aba se torna totalmente autorável, com a âncora lógica de volta no lugar. Abas sem chave continuam perfeitamente válidas para importar — este é um limite de autoria, não de esquema.

Renomeação de coluna / mudança de @type quebra o código do jogo que referencia; irreversível depois do reflect

  • O quê: o nome/tipo do campo gerado muda; o código do jogo que o referencia precisa ser atualizado manualmente. O Ctrl+Z funciona apenas antes do reflect.
  • Por quê: tipagem forte — o campo faz parte do esquema gerado. Uma quebra de compilação é capturada pelo safe-abort da cadeia automática, com uma frase acionável. Os valores da coluna são totalmente preservados (apenas as células de marcador mudam).
  • Solução alternativa: a caixa de confirmação avisa primeiro; atualize o seu código e deixe a próxima importação retomar.

Renomear aba quebra o código do jogo que referencia; irreversível depois do reflect

Mesma mecânica de antes — o nome da classe gerada muda (FooDatabaseBarDatabase); a reimportação cuida de toda a limpeza do lado dos assets automaticamente (classe antiga, SO, endereço).

Renomeações mútuas (troca) e cíclicas de aba são suportadas

  • O quê: Alpha→Beta + Beta→Alpha (uma troca), e ciclos mais longos (A→B→C→A), podem ser preparados e refletidos em um único lote — preparar qualquer uma das metades primeiro funciona, e a barra de abas mostra os nomes trocados imediatamente (WYSIWYG, desfazível). O portão da UI usa unicidade do conjunto de nomes final (apenas um conflito real — duas renomeações visando o mesmo nome — é rejeitado); o reflect impõe isso rigorosamente.
  • As referências seguem os dados (identidade da aba), não o nome: depois de uma troca A↔B, RecordId@A é reescrito atomicamente para RecordId@B (passagem única — nunca aplicado duas vezes), então continua apontando para os mesmos dados, que se moveram para B.
  • Local: uma troca intercambia o conteúdo dos dois arquivos em uma única passagem de gravação; uma cadeia que reutiliza um nome em uma extensão diferente exclui o arquivo obsoleto da extensão antiga (proteção de exclusão baseada em caminho), então a reimportação nunca vê uma aba duplicada.
  • Google: as mudanças de título são ordenadas topologicamente e quebram qualquer ciclo com um título temporário (A→tmp, B→A, tmp→B), então a planilha ao vivo nunca fica com um título duplicado momentâneo. Se uma mudança de título falhar no meio da sequência, a aba deixada sob um nome temporário é relatada com orientação de recuperação.

Caso extremo exclusivo do Google: abas trocadas que referenciam uma à outra não são redirecionadas

  • O quê: quando as duas abas trocadas referenciam uma à outra (a aba A tem uma coluna RecordId@B e a aba B tem uma coluna RecordId@A), o caminho do Google preserva o conteúdo delas via a mudança de título no próprio lugar e não reescreve suas próprias células @type — então essa autorreferência mútua não é redirecionada no Google.
  • Por quê: o Google renomeia uma aba mudando seu título (o conteúdo permanece intocado, por design); reescrever a própria grade da aba renomeada anularia isso. Origens locais reescrevem a projeção da aba renomeada, então o modo local trata isso totalmente. Referências de uma terceira aba são redirecionadas em ambos os caminhos.
  • Solução alternativa: no Google, roteie a referência mútua através de uma terceira aba, ou reflita a troca via um nome intermediário.

A sobreposição de SO de valor preparado não tem mais um botão

  • O quê: a sobreposição "pré-visualizar valores preparados nos SOs" (EphemeralSoApply) era acionada por um botão do Workbench, e aquela janela se foi. O tipo permanece como API pública para uma ferramenta que a queira; o alternador Edição de teste do inspector de SO cobre o caso do dia a dia de testar um número em tempo de execução.
  • Limite, se você a chamar: a sobreposição recusa — com um selo — (a) colunas pendentes/novas e (b) células com falha de parsing. Novas linhas são suportadas. Ela reutiliza o caminho real de parse+bake, então o que ela não consegue computar de forma verdadeira, ela recusa em vez de falsificar.
  • Solução alternativa: ela sempre foi apenas uma pré-visualização; reflita normalmente para a mudança real. A reimportação sempre restaura a verdade.

Edições preparadas se isolam quando sua linha é renomeada, excluída, ou entra em conflito de chave externamente

  • O quê: uma edição preparada cuja linha foi renomeada externamente, excluída externamente, ou entrou em conflito de chave entre a preparação e o reflect é excluída do reflect e sinalizada como "isolada".
  • Por quê: o seu endereço lógico não pode ser reresolvido — mas ela não é descartada silenciosamente, nem permitida a bloquear a sessão.
  • Solução alternativa: descarte-a individualmente (depois de uma confirmação) e prepare-a novamente.

A propagação de renomeação de chave cobre apenas células do baseline

  • O quê: texto que você acabou de digitar no mesmo lote referenciando a chave antiga não é reescrito automaticamente.
  • Por quê: reescrever silenciosamente a entrada recente do usuário é proibido; a validação prévia captura a referência pendente em vez disso.
  • Solução alternativa: corrija a referência preparada você mesmo, ou reflita a renomeação primeiro.

Os itens restantes são todos sobre o Data Studio, a única janela de autoria. A janela Workbench mais antiga foi removida; as três funcionalidades que só ela oferecia se mudaram primeiro para o Studio e o inspector de configurações — veja o que aconteceu com o Workbench.

Uma planilha que falhou na validação abre para edição — mas Exportação, Push e builds continuam bloqueados

  • O quê: se a origem foi lida por completo, suas planilhas são salvas como o baseline mesmo quando a validação falhou, então o Studio pode abri-las e você pode corrigir os erros no próprio lugar. Geração de código e bake não rodam até a contagem de erros chegar a zero, e Exportação, Push para a planilha ao vivo, e builds de jogador são todos recusados enquanto a planilha está nesse estado, cada um dizendo o porquê.
  • Por quê: essas três saídas combinam os últimos valores cozidos (bake) com sucesso com as planilhas mais novas. Rodar uma agora emendaria valores obsoletos sobre células que alguém já corrigiu — um rollback silencioso. Bloquear as saídas é o que permite que a entrada permaneça aberta.
  • Refletir uma correção pergunta uma vez: em uma planilha em quarentena, a gravação de volta mostra uma confirmação extra, porque o pre-flight não pode ser o portão rígido ali (a planilha já tem erros). Tudo que é encontrado é relatado como um aviso naquele reflect, e a reimportação automática revalida a planilha inteira. Planilhas saudáveis não são afetadas — o pre-flight continua recusando gravar.
  • Solução alternativa: corrija todo erro relatado e puxe de novo. O bloqueio se levanta sozinho no único lugar que o limpa: uma execução que se completa passando pelo bake.

A ordenação e o filtro do Data Studio são somente de exibição — e desabilitam a reordenação de linha enquanto ativos

  • O quê: a ordenação por planilha do Studio (qualquer coluna, asc/desc, persistida por projeto) e o filtro de texto mudam apenas a ordem de exibição. A margem de número de linha mantém os números de linha reais da planilha, e nenhum dos dois afeta a preparação, o reflect, o push ou a exportação. Enquanto uma ordenação ou filtro está ativo, as ferramentas de reordenação ▲▼ da linha ficam desabilitadas, com um tooltip.
  • Por quê: reordenar por "vizinho visível" enquanto a visualização está ordenada ou filtrada moveria linhas silenciosamente para perto de linhas que o usuário não consegue ver. Mudanças reais na ordem das linhas são uma operação de estrutura — limpe a ordenação/filtro primeiro.
  • Nota: "ordenar por mais recente" só existe se a sua planilha tiver uma coluna que codifique isso (por exemplo, um IntId ou uma coluna de string parecida com data) — a planilha em si não armazena timestamps.

Os problemas do Data Studio são um rascunho enquanto uma renomeação de chave está preparada

  • O quê: enquanto uma célula de chave (RecordId) tem uma edição preparada, o painel Problems carrega um selo de rascunho, e as entradas de referência não resolvida nele podem ser falsos alarmes.
  • Por quê: a pré-visualização em memória não aplica a propagação de renomeação de chave — isso roda no momento do reflect, em todas as abas. Em vez de esconder os diagnósticos ou falsificar a propagação, a janela te diz que a lista é um rascunho até a renomeação ser gravada.
  • Solução alternativa: reflita a renomeação (a propagação roda com sua própria confirmação), depois leia a lista atualizada.

A tabela é row-virtualised acima de 200 linhas — com duas arestas que vale a pena conhecer

  • O quê: acima de 200 linhas, a tabela constrói elementos de linha apenas para a janela visível (mais doze linhas de overscan), com espaçadores acima e abaixo sustentando a altura total real, para que a barra de rolagem não minta. Rolar através de um limite de janela reutiliza as linhas que sobrevivem e constrói apenas as que entraram. A grade do navegador faz o mesmo, no mesmo limite.

    Dois casos ainda constroem tudo. Em 200 linhas ou menos, toda linha é construída exatamente como antes, bit a bit. O mesmo vale para uma tabela cuja altura de viewport não pode ser consultada de forma alguma — uma parada fora de uma janela, onde o layout nunca chega — porque ali o fallback honesto é "construir tudo". Uma tabela grande que simplesmente ainda não foi disposta espera um frame em vez disso, então ela é janelada desde a sua primeira pintura, em vez de construir tudo e descartar depois.

  • A linha que você está editando permanece viva mesmo depois de rolar para fora, então o cursor, o foco e o que você digitou sobrevivem. Essa manutenção tem um limite de distância, e passado ele o editor aberto confirma e perde o foco em vez de ser carregado indefinidamente. Nada se perde quando isso acontece — o valor já está na sessão de preparação.

  • Apenas a criação de elemento é janelada. Amostragem de largura de coluna, busca, ordenação, coordenadas e a sobreposição de preparação ainda consideram toda linha, porque cada uma delas daria uma resposta diferente se olhasse apenas o que está em tela. Então trocar para uma planilha muito grande ainda faz um trabalho proporcional ao seu tamanho; o que ela não faz mais é construir milhares de widgets.

  • No navegador, o modo janelado mede as colunas em vez de deixar o layout fazer isso. Larguras de auto-layout seriam computadas a partir de quaisquer linhas que estivessem na janela, então uma coluna tremeria conforme você rolasse. No modo janelado as larguras vêm de uma estimativa orientada por dados sobre todas as linhas, e então são fixadas. O modo de renderização completa (≤ 200 linhas) continua usando auto-layout, inalterado.

O canvas só faz pan dentro do seu intervalo de scroll, e seus fios tracejados de ciclo ficam mais grossos conforme crescem

  • O quê:

    • Ctrl/Cmd + roda do mouse dá zoom no canvas de registros entre 25% e 200%, mantendo parado o ponto sob o cursor — um zoom ancorado no centro deslizaria para fora da tela o card que você estava olhando. A porcentagem no cabeçalho do canvas é um botão que volta para 100%. Uma rolagem simples da roda continua fazendo scroll.
    • Arrastar com o botão do meio do mouse — ou Alt + botão esquerdo, para hardware sem um botão do meio — faz pan, e o cursor marca a preensão enquanto ela é mantida. O arrastar com o botão esquerdo fica reservado para selecionar e vincular, então ele não poderia também significar "mover a visualização".
    • O painel é uma scroll view, então o intervalo de pan é o intervalo de scroll: ele para na borda do conteúdo, em vez de derivar para o espaço vazio, e quando o conteúdo é menor que a viewport ele simplesmente não se move. Isto não é um canvas infinito.
    • Um fio tracejado marcando um ciclo limita quantos traços ele desenha e dobra o seu período de traço em um caminho longo, então um loop muito longo aparece mais grosso, em vez de mais nítido.
  • Por quê: a Unity aloca vértices de mesh por chamada de desenho, com um teto rígido de 65,535, e ultrapassar isso faz o desenho desaparecer por completo, mesmo continuando a pagar pela tesselação. O limite de traços mantém um único Stroke dentro desse orçamento por design.

    A grade de pontos de fundo costumava estar no mesmo precipício e não está mais: ela é um pequeno tile de fundo repetido, que custa zero vértices e repinta em tempo constante, não importa quão grande o canvas cresça. (Um ponto desenhado como um caminho custa, medido, 28 vértices, não os seus quatro cantos — a aritmética por trás do orçamento de 1,800 pontos do fallback de desenho, e o motivo pelo qual o tile é o caminho distribuído.)

  • Solução alternativa: nenhuma necessária para a grade. Para uma vizinhança grande, dê zoom out, estreite o segmento de direção, ou abra um vizinho como o novo terminus, em vez de tentar encaixar tudo em uma única tela.

Uma planilha ainda sem tabela é ignorada, não importada

  • O quê: uma aba que não tem nenhum dos três marcadores obrigatórios e nenhuma linha de dado — uma planilha totalmente nova contendo apenas comentários ou uma linha @style — é ignorada com um aviso EmptyTabSkipped, em vez de fazer a importação falhar pelos três marcadores ausentes. Seu código já gerado, asset cozido (bake) e endereço são preservados, não limpos como se a aba tivesse sido excluída. Exportação e Push a ignoram simetricamente, porque os três perguntam o mesmo predicado.
  • Por quê: uma planilha inacabada não pode ser capaz de impedir que toda outra aba seja importada, e um autor normalmente cria a planilha antes da sua linha de cabeçalho.
  • Limite: uma planilha parcialmente escrita (com qualquer marcador obrigatório presente) não é ignorada — ela falha honestamente, porque ignorá-la silenciosamente esconderia trabalho real. Uma planilha digitada a partir da coluna A, em vez da coluna B, é igualmente entregue ao parser, então o seu diagnóstico real ("a coluna A é a coluna de marcador, os dados começam em B") sobrevive.

Um enum registrado a partir de C# de plugin não pode ganhar membros de uma planilha

  • O quê: um Enum<T> cujo T um plugin registrou com enums.Register<T>() pertence ao código. Uma planilha de definição de enum não pode reivindicar esse nome (DuplicateEnumName), e a linha "Add a new member…" do dropdown da célula simplesmente fica ausente nessa coluna.
  • Por quê: a planilha é canônica somente para o que a planilha define. Escrever um membro em uma planilha que não decide mais o tipo compilado produziria um membro que nunca aparece no código — uma promessa que o produto não consegue cumprir. A linha ausente é como a UI diz isso, em vez de oferecer uma ação que falharia.
  • Solução alternativa: mova o enum para uma planilha de enum, se a planilha deveria ser a dona dele, ou adicione o membro no C# do seu plugin e recompile.
  • A estrutura segue a mesma linha de propriedade: a estrutura de uma planilha de definição de enum é totalmente autorada nos dois hosts — definir, renomear, excluir, reordenar colunas, editar o tipo subjacente e a descrição — mas nada disso consegue tocar um nome de enum pertencente ao código, e uma planilha de definição também não consegue reivindicar um. A recusa nomeia o motivo.

Planilhas de definição de enum: membros só anexam, e não há ordenação

  • O quê: a estrutura da planilha de enum é autorada no próprio lugar tanto no editor quanto no aplicativo web, mas as linhas de membro só sempre anexam — um espaço vazio nunca é preenchido de volta — e a visualização não oferece ordenação nem filtro.
  • Por quê: a posição de um membro é o seu valor inteiro. Preencher um espaço vazio ou reorganizar membros renumeraria silenciosamente valores já cozidos (bake) em assets e armazenados em saves. A ordem de coluna, em contraste, não carrega significado nenhum, motivo pelo qual reordenar colunas é sempre permitido.
  • Solução alternativa: para fixar um valor explicitamente, use a sintaxe Name=value; a ordem de apresentação em outro lugar é preocupação do consumidor, não da planilha.

Um enum definido em planilha sempre gera na pasta de configurações

  • O quê: tipos gerados de aba regeneram no próprio lugar, em qualquer pasta em que já vivem, mas o arquivo de enum (SheetForgeEnums.cs) não tem nenhuma aba à qual se ancorar, então ele é sempre gravado na pasta de código gerado nomeada nas configurações. Se uma aba cujo código gerado vive na sua própria pasta de pacote usa um enum definido em planilha, aquele assembly do pacote falha ao compilar com CS0246.
  • Por quê: um único arquivo guarda todo enum definido em planilha, porque um enum é uma saída em nível de projeto, e não uma por aba — então não existe uma única aba cujo lar ele pudesse seguir.
  • Solução alternativa: coloque as duas pastas geradas em um único assembly, ou registre aquele enum a partir de código de plugin em vez disso. A falha é um erro de compilação visível, com o tipo ausente nomeado, nunca uma corrupção silenciosa.

Referências de asset tipadas resolvem contra os tipos carregados — nome curto somente quando único, sem tipos de assembly predefinido

  • O quê: AssetRef@Group<Type> aceita qualquer tipo de asset derivado de UnityEngine.Object que o projeto consegue carregar, do engine ou seu próprio, sem lista de permissões. Três coisas são recusadas em vez de adivinhadas: um nome curto compartilhado por vários tipos carregados (AmbiguousAssetTypeTextAsset pode ser um deles, dependendo dos pacotes instalados) precisa ser escrito como o nome completo (UnityEngine.TextAsset); um nome desconhecido é UnknownAssetType, com uma sugestão de correspondência mais próxima; e um tipo que vive em um assembly predefinido (Assembly-CSharp e os demais do mesmo tipo — qualquer pasta de script sem uma assembly definition) é AssetTypeNotReferenceable.
  • Por quê: o assembly complementar gerado é uma assembly definition, e uma assembly definition não pode referenciar os assemblies predefinidos — AssetReferenceT<T> para um T desses não compilaria. Resolver um nome ambíguo escolhendo um deles vincularia a coluna silenciosamente ao tipo errado.
  • Solução alternativa: mova o tipo para uma assembly definition, ou remova a restrição <…> e mantenha o AssetRef@Group sem restrição. Componentes e tipos exclusivos do editor nunca são candidatos.
  • Além disso: o navegador não resolve nomes de tipo (ele não tem projeto contra o qual resolver): o aplicativo web faz o parsing de <Type> e o mostra no tooltip da coluna, mas não produz nenhum dos três diagnósticos de nome de tipo e não oferece seletor nem soltar. O codegen nunca emite um nome não resolvido ao pé da letra — um nome que não consegue resolver recorre a AssetReference com um aviso AssetTypeUnresolvedFallback.

O seletor de asset, o soltar e os registros preparados — o que é e o que não é automático

  • O quê: soltar ou escolher um asset grava o endereço na célula na hora e prepara a alteração no Addressables (adicionar · mover · criar grupo) para o reflect; a alteração só roda depois que a gravação da planilha for bem-sucedida — ou, quando registros são a única coisa preparada, por conta própria, seguida da reimportação automática; um reflect que não conseguiu gravar porque as suas abas são baseadas em workbook mantém os registros preparados. Um registro que nada mais referencia é descartado quando a célula é retipada, e qualquer um que sobreviva até o reflect é ignorado por não estar mais referenciado; um asset já presente no grupo mantém o seu endereço existente; um asset em outro grupo só é movido depois de uma confirmação que nomeia as outras células que o referenciam. O endereço automático é o nome do arquivo sem a extensão, e um endereço já usado por um asset diferente naquele grupo é recusado, em vez de renomeado. Grupos novos recebem os BundledAssetGroupSchema e ContentUpdateGroupSchema padrão. Itens aplicados e ignorados são registrados no Console com os seus motivos, e para uma origem de pasta local, o diálogo de conclusão do reflect repete o resumo (Addressables: N registered, M skipped).
  • Por quê: a planilha é canônica — o projeto nunca deve mudar para um reflect que não chegou até a planilha, e uma entrada que nenhuma célula aponta seria um órfão que a planilha não explica.
  • Solução alternativa: se um registro foi ignorado, a próxima reimportação relata a célula como UnknownAssetKey; corrija a causa e reflita de novo. O registro sempre exige o pacote Addressables.

Sub-assets são endereçados como parent[sub], e uma textura no modo Sprite passa em <Sprite>

  • O quê: uma entrada de sub-objeto (um sprite em uma textura, um material em uma fonte) é endereçada da forma como o Addressables a nomeia — parent[sub] — e essa chave é verificada contra o próprio tipo do sub-objeto. O endereço do pai satisfaz o seu próprio tipo e todo tipo de sub-asset que ele contém, o que é o que permite que uma textura importada no modo Sprite passe em uma coluna <Sprite>. Soltar um sub-asset prepara o pai para registro e grava parent[sub] na célula.
  • Limite: uma entrada de sub-objeto precisa existir no catálogo do Addressables para que parent[sub] valide; o seletor lista as sub-chaves que conhece depois do seu pai.

Cores não têm HDR, tangentes de curva seguem o seu modo, gradientes são quantizados — por design

  • O quê: Color é quatro bytes — um canal acima de 1 (HDR) é limitado a 0…1 na Exportação. Uma tangente de AnimationCurve cujo lado é Auto, Linear, Constant ou ClampedAuto é recalculada a partir do modo na importação, então um número digitado à mão que contradiz o seu modo é substituído (o mesmo recálculo que a Unity realiza quando o modo é aplicado); Once é lido como ClampForever e nunca é gravado de volta; uma curva sem chaves não tem forma de texto e existe apenas como a célula vazia de uma coluna opcional. Os tempos de chave de Gradient são quantizados para 16 bits na importação (exatamente como a Unity os armazena), um gradiente de chave única volta da Unity como duas chaves idênticas, e o espaço de cor só é gravado quando foi definido.
  • Por quê: o valor que a planilha mostra precisa ser o valor que o engine guarda, então a normalização que a Unity faria depois é feita uma única vez, na entrada, e toda superfície — planilha, campo do editor, prévia web, asset cozido (bake) — mostra uma única curva e um único gradiente.
  • Solução alternativa: use Free/Free quando você quiser que os números de tangente sejam levados ao pé da letra; armazene intensidades HDR em uma coluna float separada.

Editores de chips e campos nativos existem apenas para os três tipos visuais

  • O quê: o Data Studio mostra os escalares Color, AnimationCurve e Gradient como os próprios campos da Unity, e os seus List<> como editores de chips; o aplicativo web mostra prévias com os seus próprios editores e listas de chips. Toda outra coluna de lista — List<int>, List<Enum<…>>, listas de wrapper — permanece como texto canônico nos dois hosts, e um wrapper que contém um dos três (Pair<Color>) também é texto.
  • Por quê: os três tipos são os que têm uma imagem por elemento; para o resto, uma única linha canônica já é a representação mais exata, e a notação externa de um wrapper pertence ao seu plugin.
  • Espaço: um tipo de plugin que armazena um dos três valores pode adotar os mesmos editores declarando o arquétipo StudioCellEditorHint correspondente (veja Autoria de Plugins §4.16).

A coloração de propriedade é apenas planilha-versus-código

  • O quê: a barra lateral/legenda distingue exatamente duas origens — abas de planilha reais e abas virtuais de registro de código. Não existe uma terceira classificação "gerada", nem coloração de propriedade por coluna.
  • Por quê: a origem é derivada da própria aba, que a janela já conhece com certeza; uma classificação por coluna precisaria de mais um ponto de extensão para ser verdadeira, e nenhum consumidor pediu um.
  • Não é a mesma coisa que a cor de uma planilha: o @style permite que uma planilha nomeie a sua própria cor, e essa cor é um metadado de exibição que o autor escolheu — ela não diz nada sobre de onde a planilha vem. As duas colorações são lidas de lugares diferentes e nunca se fundem.

Os dropdowns de referência respondem pertencimento, não ordem

  • O quê: uma célula RecordId@Tab agora tem um dropdown pesquisável (e uma checklist para List<>), mas o dropdown da célula de lista apenas adiciona e remove elementos — ele não consegue mover um. Reordenar uma lista é feito no canvas, onde cada elemento tem a sua própria linha.
  • Por quê: um dropdown responde "o que há aqui dentro"; "qual posição" precisa de uma superfície que alinhe as coisas, que é o que o canvas já é. Duplicar isso em dois lugares seriam duas respostas para manter.
  • Também: o dropdown se anexa apenas a uma coluna de referência simples, sem wrapper. O texto de uma célula wrapper carrega a própria notação do wrapper, então colar uma chave nua nela destruiria o valor — alcançar o interior de um wrapper é trabalho de um widget de célula registrado (veja Autoria de Plugins).

A busca All lê dados cozidos (bake) no editor e a sessão ao vivo no navegador

  • O quê: a entrada All do Data Studio busca os bancos de dados cozidos (bake), então precisa de uma importação bem-sucedida primeiro e se atualiza sozinha quando uma importação é concluída ou as configurações ativas mudam. A entrada All do aplicativo web busca os valores que a sessão mostra agora mesmo, edições preparadas incluídas. Correspondência, ordem dos resultados e a página de 50 linhas são o mesmo código nos dois, e nos dois um resultado é aberto com duplo clique (ou Enter na linha selecionada) para abrir aquela planilha com a célula correspondente selecionada.
  • Por quê: o navegador não tem ScriptableObjects cozidos (bake); o que ele tem é a sessão ao vivo — e responder com o valor em tela é para isso que serve uma sessão de navegador. O editor continua lendo a verdade cozida (bake) que já tem.
  • Fronteira: no editor, uma edição que está preparada mas ainda não refletida não é encontrada pelo All até que uma importação tenha rodado, e um resultado de uma planilha que a sessão não carregou não navega — um aviso abaixo da lista diz o porquê. No navegador, uma edição preparada é encontrada na hora e todo resultado pode ser aberto.

5. Desempenho

  • O caminho normal é linear e rápido: 50,000 linhas × 20 colunas ≈ 628 ms (editor em execução, Mono; 144 ms headless); 50 abas × 2,000 linhas com 180k células de referência ≈ 294 ms. Tamanhos típicos de projeto não são um problema.
  • A memória é linear, mas com bastante boxing: ≈ 59 bytes/célula retidos (≈ 138 no pico durante a importação). No teto de células do Google Sheet (~10M células) isso se extrapola para ~6.3 s de importação, ~590 MB retidos, ~1.4 GB no pico — atenção a contextos de baixa especificação/32 bits em tamanhos extremos. (Um IR orientado a coluna é um item reconhecido do backlog.)
  • A tabela de autoria é row-virtualised acima de 200 linhas, tanto no editor quanto no navegador, então abrir uma planilha grande não constrói mais um widget por linha. O que não é janelado é a lógica por linha que responderia de forma diferente se fosse — amostragem de largura, busca, ordenação, coordenadas, a sobreposição de preparação. Detalhes e as duas arestas estão em §4.
  • O caminho de erro também é linear: mesmo quando referências quebram em massa, o cálculo de sugestão mais próxima permanece limitado — um orçamento de sugestão por campo somado a uma distância de edição pré-filtrada por comprimento e com encerramento antecipado mantêm isso aproximadamente linear no número de referências quebradas (≈ 45 ms para 4,000 referências quebradas, headless; dados válidos na mesma escala ≈ 2.7 ms). Renomear uma aba referenciada não chega a quebrar referências em massa, para começo de conversa: a renomeação reescreve as células @type que fazem a referência.

6. Cena de demonstração

  • Os exemplos são importações seletivas — os dois exemplos de demonstração e suas cenas não estão presentes por padrão. Eles são distribuídos como pacotes Unity (Assets/SheetForge/Examples/SheetForgePluginDemo.unitypackage e SheetForgeCoreDemo.unitypackage); importe um deles (duplo clique, ou o botão Importar Plugin/Core Demo da janela Primeiros Passos) para restaurar Assets/SheetForge.PluginDemo/… ou Assets/SheetForge.CoreDemo/…. Até lá, os exemplos não estão no seu projeto de jeito nenhum — eles são distribuídos apenas como esses pacotes — então eles nunca podem colidir com o seu projeto — o produto principal é totalmente autossuficiente sem eles.
  • Exige uma importação primeiro (por máquina) — os endereços do Addressables que ele carrega são um cache não versionado. Antes disso, ele mostra uma mensagem de orientação.
  • Nenhuma configuração de namespace necessária para os exemplos — os tipos de exemplo versionados usam o namespace padrão SheetForge.Generated com um prefixo de classe Example*, então reimportar um exemplo os regenera no próprio lugar sem precisar de nenhuma configuração generatedNamespace.

7. Extensão de plugin — costuras disponíveis (com limites) e o que ainda é reservado

Dezesseis contratos de extensão estão disponíveis, cada um deles se integrando com zero edições no Core — o conjunto completo está em Autoria de Plugins. Todos os dezesseis são encontrados por descoberta (o TypeCache da Unity; a varredura de assembly enviado do navegador), sem referência de assembly e sem manifesto para editar. Os onze do Core então recebem um registry no qual registrar o que adicionam, enquanto os cinco do Editor — widget de grafo, ação de inspector, provedor de editor de célula, provedor de painel e provedor de origem — são simplesmente descobertos e usados como são.

Cinco das costuras disponíveis carregam limites que vale a pena declarar aqui, em vez de deixar você descobri-los:

Referenciando tipos de célula personalizados — paridade completa com RecordId@Tab para a sua própria notação

  • O quê: um parser de célula registrado que também implementa IReferencingCellType (e cujo valor implementa IRefBearingValue) diz ao Core como ler e reescrever a chave enterrada na sua própria notação. Essa coluna então ganha tudo que uma referência embutida ganha:

    • checagem de integridade com sugestões de correspondência mais próxima;
    • propagação de renomeação de chave que preserva o payload (attack:add:10power:add:10);
    • arestas e portas de grafo, o seletor , contagens de referência reversa;
    • detecção de órfãos e regras de dropdown exportadas.

    A descoberta é um cast do parser já registrado — não há novo canal de registro, e um tipo personalizado que não a implementa fica inalterado bit a bit. Veja Autoria de Plugins §4.4a.

  • Limites: um payload não pode conter ; — o Core divide uma célula de lista em elementos antes mesmo do seu parser ver o texto, então um ponto e vírgula dentro de um valor seria despedaçado em dois elementos (a mesma restrição que os tipos wrapper carregam). E @target precisa nomear uma aba de planilha real: a aba virtual de um registro de código é rejeitada com UnknownTargetTab, exatamente como acontece para RecordId@Tab. Essa restrição é o que permite que o próprio relato de referência não resolvida e a propagação de renomeação do Core se apliquem sem modificação.

Tipos wrapper <> (MyWrapper<T>) — com regras de rejeição

  • O quê: um plugin registra uma forma de valor genérica (por exemplo, Pair<int> = 1~2) via ICellWrapperType; o Core resolve o tipo interno recursivamente (veja Sintaxe da Planilha).

  • Regras de rejeição:

    • Pair<List<T>> é rejeitado — uma lista não pode ficar dentro de um wrapper, e List permanece plana e no nível mais externo.
    • Pair<int>@Tab é rejeitado — coloque o @ na folha interna: Pair<RecordId@Tab>.
    • Pair<int?> / Pair<int=1> são rejeitados — opcionalidade/padrões são em nível de campo, não parte do tipo interno.

    List<Pair<T>> é permitido, mas o delimitador do próprio wrapper precisa ser diferente de ; (o separador de lista) — uma responsabilidade de quem cria o plugin que o Core não consegue impor.

Marcadores estruturais personalizados (@yourMarker) — apenas para metadado por coluna

  • O quê: um plugin registra uma linha @marker via IStructuralMarkerDefinition / ISheetForgeMarkerPlugin, generalizando a validação por coluna do @overlap. O valor é armazenado como metadado FieldSchema.MarkerValues.
  • Limites: um marcador possui apenas sua validação de valor por coluna — ele não assume o parsing de uma forma de dado de linha inteira (a normalização continua sendo a forma de expressar formas de dado). E o codegen não faz o bake de valores de marcador: assim como @overlap, eles são apenas metadado de validação/exibição, invisível ao fingerprint do esquema — então nada relacionado a marcador chega ao código gerado ou ao SO do bake.

Predefinições de cor — as superfícies que nós pintamos, não os widgets da Unity

  • O quê: um plugin registra uma predefinição de cor via ISheetForgeThemePlugin / ThemeRegistry; ela aparece em Preferences ▸ SheetForge ▸ Theme ao lado das predefinições embutidas Default e High contrast, e só se aplica se o usuário a escolher (registrar nunca sequestra a tela). Uma predefinição sobrescreve apenas os slots que ela nomeia — todo outro slot mantém o padrão do produto, então predefinições continuam válidas conforme slots são adicionados.
  • Limite — chrome misto é esperado: o tema cobre o que o próprio SheetForge pinta (fundos de janela, cabeçalhos, texto, destaques, cores de grade e de preparação). Widgets nativos da Unity desenhados dentro dessas janelas — chrome de botão, bordas de campo, setas de popup — continuam seguindo a skin do editor, que a Unity não deixa um pacote reestilizar. Então escolher Always light enquanto o editor roda a skin escura dá uma superfície clara do SheetForge com widgets nativos escuros sobre ela. Ajuste a skin do editor para combinar, se você quiser uma aparência uniforme.
  • Limite — somente cor: predefinições carregam cores (0xRRGGBB por slot). Espaçamento, tamanhos de fonte e layout não são temáveis, e preenchimentos translúcidos (fundos de selo, o scrim do modal) derivam de uma cor de slot mais um alfa fixo, em vez de serem configuráveis separadamente.

Superfícies de autoria declarativas — um vocabulário limitado, de propósito

  • O quê: ISheetForgeStudioPlugin deixa um pacote descrever verbos, painéis, selos de coluna e formas de editor de célula como dado, então um único registro é desenhado tanto pelo editor quanto pelo navegador. O vocabulário é fixo e cresce apenas por adição: cinco posicionamentos de ação, treze tipos de nó, sete arquétipos de editor de célula (os dois mais novos, CurveEditor e GradientEditor, são os que os tipos embutidos de curva e gradiente usam).
  • Limite — não é um framework de UI. Renderização arbitrária, entrada composta e fluxos de múltiplas etapas não têm palavras aqui, e adicioná-las significaria manter um mini toolkit de UI para sempre. É para isso que serve o IStudioPanelProvider: registre-o sob o mesmo id de um painel descrito, e o editor desenha o rico enquanto o navegador desenha o descritivo. Não existe uma válvula de escape exclusiva da web — o navegador não consegue carregar um tipo UIToolkit, e fingir o contrário colocaria a extensão de um plugin em apenas uma tela.
  • Limite — os poderes de uma ação são exatamente quatro: preparar uma célula, preparar várias células como um passo de Undo, focar um registro, pedir um redesenho. O verbo de um plugin é, portanto, uma edição preparada comum que passa pelo mesmo portão, pre-flight e push que uma digitada à mão. A própria sessão de autoria é deliberadamente não exposta.
  • Zero-disparo, nunca disparo errado, para observadores: IPipelineObserver dispara ao final de um ciclo de importação explícito. Dois caminhos nunca alcançam esse ponto: uma execução que para antes de o pipeline começar (nenhuma configuração ativa; Addressables não instalado) e uma etapa codegen→compilação interrompida por um erro de compilação. Se você precisa de "uma importação foi tentada", combine isso com o barramento ImportEvents do lado do editor.

Ainda projetado, mas não construído (nenhum consumidor ainda)

  • Marcadores de forma de dado de linha inteira (por exemplo, um marcador que lê uma matriz 2D como um único campo) — deliberadamente não construído: um marcador possui validação por coluna, não parsing de linha, e a normalização (referências + coluna type + List<T>) é expressivamente completa. A própria costura de registro do MarkerRegistry está disponível; apenas essa interpretação de parsing de forma é reservada.
  • Fazer o bake de valores de marcador personalizado no código gerado — fora do escopo até que um consumidor precise de metadado de marcador como constantes/atributos de codegen.
  • Contêineres de SO por registro / carregamento preguiçoso (lazy) — design completo, não construído; o codegen atual produz apenas Database SOs de carregamento total por aba.

8. Evolução de esquema

  • Exportação/Push exigem um bake atualizado depois de uma mudança de esquema — um bake desatualizado falha com ExportSchemaMismatch (incompatibilidade de fingerprint). A recusa agora oferece executar essa importação para você: uma confirmação a inicia, e nada é exportado ou enviado por push automaticamente depois disso — você pressiona a ação original de novo assim que a importação for concluída.
  • A primeira importação depois de uma mudança de esquema é internamente em dois estágios (codegen → compilação → bake) — automática, uma ação do usuário; apenas uma falha de compilação a interrompe (safe abort, frase acionável, limite de 3 tentativas).

9. Escopo da localização

Fragmentos de detalhe do relatório (valores problemáticos, sugestões), exceções de baixo nível, e logs de desenvolvedor ficam em inglês embutido dentro do esqueleto localizado do relatório — interpolações em tempo de execução não podem ser chaves de tabela de idioma (limite padrão da indústria). Tudo que é desenhado pelo editor, mais o esqueleto do relatório e as frases de por quê/como, é totalmente localizado nos 10 idiomas.

Os dez idiomas estão traduzidos por completo. Cada chave em cada tabela de idioma carrega uma tradução real — o Data Studio, as preferências de tema, diálogos e linhas de log inclusive. A paridade de chaves entre os dez arquivos é imposta por teste, então nada recorre a uma chave bruta nem quebra um placeholder.

Um punhado de entradas por idioma realmente se lê exatamente como a versão em inglês, e isso é uma decisão de tradução, não uma passagem pendente: são símbolos e strings que servem apenas de placeholder (, +), nomes próprios e nomes de formato (Google Sheets, SHA-256), e palavras que um idioma genuinamente escreve como no inglês (OK, Alpha).

Os próprios rótulos de um plugin não estão na tabela do Core de forma alguma: registre-os com ISheetForgeStringsPlugin para que sigam o idioma do usuário, ou deixe-os sem registro e eles são mostrados ao pé da letra.

10. Interações do editor

  • Escopo do Ctrl+Z: um campo de texto em foco consome o Ctrl+Z primeiro (padrão do SO); depois de um reflect bem-sucedido, o histórico de preparação é limpo — o undo nunca alcança o que já foi gravado na planilha (a planilha é canônica).
  • A mudança de idioma fica bloqueada durante Importação/Exportação/Push (ela dispara uma recompilação de regeneração de menu). Mudanças de tema não ficam bloqueadas — elas nunca recompilam, então o brilho e a predefinição podem ser trocados a qualquer momento, inclusive no meio de uma execução.
  • Tema e idioma são por usuário (EditorPrefs), não por projeto — cada colega de equipe mantém o seu próprio, e nenhum dos dois aparece no controle de versão. Escolher uma predefinição de cor que não seja a padrão grava uma folha de estilo gerada em Assets/SheetForge/Editor/Generated/ (gitignored, autorreparável); a predefinição padrão não grava nada e a exclui.
  • Código gerado + SOs do bake + o grupo Addressables são caches gitignored, por máquina — cada máquina executa uma importação uma vez; o código do jogo carrega por endereço, nunca por referência direta de cena.

11. Licenciamento

O repositório distribui um aviso LICENSE: o EULA da Unity Asset Store é o acordo vigente, com um aviso de visualização do repositório (código-fonte visível para referência e para compradores licenciados; nenhuma redistribuição/revenda fora do EULA sem permissão por escrito). Código de terceiros: nenhum — incluindo o leitor/gravador de xlsx OOXML escrito à mão.

12. Estado verificado (no lançamento)

  • Harness de teste duplo: 2,150 testes headless de .NET (2,150 aprovados) + 3,021 testes EditMode (3,021 aprovados, 0 falhas, 4 ignorados). Esses dois números são o único lugar onde as contagens são declaradas; toda outra página tem um link para cá.
  • Os quatro ignorados são o round-trip ao vivo do Google, que só roda quando credenciais de conta de serviço estão presentes no ambiente, e foi ignorado nesta execução. Com credenciais, ele já foi exercitado repetidamente contra uma planilha real — busca → push, incluindo a detecção de conflito de linha movida, o tratamento de float independente de localidade, um lote misto entre abas, e uma execução completa do dispatcher afirmando um relatório fundido e exatamente uma reimportação automática por envio. Resultado: 4/4 aprovados.
  • O app web tem os seus próprios portões, todos verdes: verificação de tipo, lint, 414 testes unitários, uma publicação WebAssembly + execução de smoke test (que carrega uma DLL de plugin real), um build de produção, 56 testes de navegador de ponta a ponta, e uma checagem de constantes entre linguagens (seis pares Unity↔web: versão do host, formato de plugin, versão de esquema do registro, escopo do OAuth, nomes de assembly do host, limites de janela de linha).
  • Todos os asmdefs compilados do produto: 0 erros, 0 avisos. (Os asmdefs de exemplo — dois em cada demo — só existem quando você importa um pacote de demonstração, e não são compilados até então.)
  • Testes de proteção passando: zero literais em coreano no código-fonte do produto, zero vocabulário de domínio nas costuras do kernel (ambos ignoram /Samples~/ — o exemplo é conteúdo de domínio), paridade de chaves nos 10 idiomas.
  • O assembly de simulação de consumidor sem IVT compila apenas contra a API pública (imposto pelo compilador), e uma sonda de mini-plugin sem IVT ali implementa quinze dos dezesseis contratos de extensão (ISheetForgePlugin / validator / edge / marker / template / graph / code registry / theme / studio UI / strings / pipeline / ISheetSourceProvider / Studio widget / Studio inspector action / Studio cell editor) usando apenas a superfície pública — então a publicidade do contrato permanece comprovada, mesmo que o exemplo Plugin Demo não esteja compilado. O décimo sexto, IStudioPanelProvider, retorna um VisualElement e é exercitado por um teste do lado do editor em vez disso. A mesma sonda também implementa as interfaces de capacidade opcionais, incluindo IReferencingCellType / IRefBearingValue, e as exercita através da superfície pública (descoberta por cast, os cinco hooks, preservação de resíduo).

Itens que só podem ser verificados no momento da submissão à Asset Store (não no repositório): o comportamento do aviso de instalação do .unitypackage, a declaração de dependência do Portal, a exclusão dos assemblies de teste do pacote distribuído, e uma nova checagem de 0 avisos em um projeto limpo.

13. Planilhas de localização (texto do jogo)

Os limites das Planilhas de Localização e da ponte do Unity Localization, declarados com honestidade. (O próprio pacote é opcional — veja o §1.)

A ponte é de mão única, e edições externas na tabela são questionadas — nunca mescladas

  • O quê: a sincronização vai apenas planilha → StringTables. A ponte carimba as tabelas que possui e registra uma impressão digital a cada sincronização; uma tabela editada por qualquer outra coisa desde então — a janela Localization Tables, a própria extensão do Google Sheets da Unity, uma importação XLIFF — faz a próxima sincronização parar e perguntar: sobrescrever a partir da planilha, ou abortar com um relatório de diferenças.
  • Por quê: duas interfaces graváveis sobre os mesmos dados terminam em sobrescritas silenciosas. A planilha é canônica, então a outra interface precisa ser explícita, não silenciosa.
  • Solução alternativa: encaminhe as traduções pela planilha — a pasta de trabalho de tradução (exportação xlsx + reimportação parcial somente de idiomas) existe exatamente para isso.

Uma chave renomeada fora do Studio é uma exclusão mais uma adição

  • O quê: renomear uma chave no Data Studio renomeia a entrada da tabela no lugar, preservando o id interno ao qual as referências LocalizedString se vinculam — as referências de cena sobrevivem. Renomear a chave diretamente na fonte da planilha (Google Sheets, Excel) é indistinguível de remover uma chave e adicionar outra: a ponte cria uma entrada nova e a antiga se torna órfã, com as referências de cena ainda apontando para a órfã.
  • Por quê: uma comparação em nível de texto não consegue distinguir uma renomeação de uma exclusão-mais-adição sem adivinhar, e um palpite errado religaria referências silenciosamente.
  • Solução alternativa: renomeie chaves no Data Studio (qualquer um dos dois hosts); o relatório de órfãs captura as consequências de uma renomeação externa.

Chaves órfãs na tabela são preservadas por padrão

  • O quê: uma chave presente na StringTable, mas que não está mais na planilha, é mantida, relatada como órfã, e removida apenas por meio da ação explícita de limpeza — ou automaticamente, se você optar pela configuração excluir ao sincronizar. Nada é excluído como efeito colateral.
  • Por quê: uma linha ausente na planilha pode ser um engano no meio de uma edição; destruir traduções por causa disso seria irrecuperável.

Somente StringTables — AssetTables não são cobertas

  • O quê: a ponte preenche coleções StringTable. O eixo AssetTable do Unity Localization (sprites, áudio e prefabs localizados) não é sincronizado a partir de planilhas. Um item reconhecido no backlog.
  • Solução alternativa: gerencie as AssetTables com as próprias ferramentas do pacote; elas não são tocadas pela ponte.

XLIFF e pseudo-idiomas não são reimplementados

  • O quê: as tabelas sincronizadas são tabelas comuns do Unity Localization, então a exportação/importação XLIFF e a pseudolocalização do próprio pacote funcionam sobre elas sem alteração. O SheetForge não adiciona uma segunda implementação.
  • Limite: a saída que essas ferramentas gravam dentro das tabelas conta como uma edição externa (primeiro item acima). Mantenha a planilha como a fonte da verdade e leve as traduções de volta pela pasta de trabalho de tradução.

Nenhum auxiliar de Smart Format específico de idioma é incluído

  • O quê: a coluna smart marca uma entrada como uma Smart String, mas o SheetForge não fornece formatadores gramaticais próprios — a seleção de partículas coreanas, por exemplo, não está incluída, deliberadamente.
  • Solução alternativa: os pontos de extensão de Smart Format do próprio pacote continuam totalmente disponíveis para formatadores que você escrever.

Constantes de chave são higienizadas para ASCII

  • O quê: as constantes {Tab}Keys geradas transformam todo caractere fora das letras ASCII, dígitos e _ em _ (colisões recebem um sufixo numérico), então uma chave não-ASCII resulta em um nome de constante ilegível — a própria chave continua funcionando em todo lugar.
  • Solução alternativa: mantenha as chaves em ASCII (ui.ok, dialog.intro) se você usa as constantes. Assim como o arquivo de enum definido pela planilha, o arquivo de constantes sempre é gerado na pasta de configurações — a mesma observação sobre assembly se aplica.

O aplicativo web autora planilhas de localização; a sincronização é do editor

  • O quê: autoria, validação, cobertura, cunhagem, a lente de idiomas e a pasta de trabalho de tradução funcionam todos no navegador. Gravar StringTables não — o navegador não tem um projeto Unity no qual gravar.
  • Por quê: escopo honesto, não um recurso ausente: as tabelas vivem no projeto.

Páginas relacionadas