triad-core
Núcleo · Lua 5.4
Operações financeiras são nomeadas e idempotentes, com identificador de transação do servidor e registro no razão.
- Lua
- 2.005 linhas
- Superfície pública
- 28 exports
tudo o mais é inalcançável de fora
- Primitivas testáveis fora do jogo
- 7
- Migrations do núcleo e da economia
- 6
Fonte verificada no repositório interno em 08/09/2026
O que faz
Em uso
O núcleo entrega o que todo resource da TRIAD usa: chamada remota do cliente com validação de esquema obrigatória, acesso a banco com consulta paralela, movimento econômico atômico, limite de frequência por balde de tokens, tipo de resultado padronizado, log e rastreio.
A superfície pública é enumerada e versionada. Nas bases de referência a superfície interna inteira sai por uma porta só, e a partir daí qualquer mudança interna quebra o ecossistema. Aqui, mudar o interior não quebra consumidor; mudar o contrato exige subir a versão da API e escrever nota de migração.
A mesma fronteira é a fronteira comercial: o que é contrato é documentado e estável, o interior vai fechado, e a configuração fica de fora para o operador do servidor editar.
Evidência
Como o sistema aparece
Capturas da interface real, renderizada com payload de demonstração derivado dos contratos do resource — organizações, cargos, pessoas e identificadores são sintéticos. Onde não existe interface, entra visualização técnica, rotulada como tal.
Visualização técnica O que é contrato e o que é interno — a mesma linha que separa o que pode ser usado por terceiros.
Visualização técnica Por que não existe "somar dinheiro": a idempotência mora no índice único do razão.
Explore a decisão
A forma da chamada também tem custo.
Buscar cada item separadamente atravessa a fronteira uma vez por item. Um contrato que aceita uma lista reúne os pedidos. A conta abaixo ilustra apenas o número de chamadas.
- Uma chamada por item
- 8
- Uma chamada com a lista
- 1
Ilustração aritmética, não benchmark. Não estima latência, FPS ou ganho real. As medições históricas e seus limites estão nas decisões do case.
Arquitetura
Arquitetura e responsabilidades
Camada por camada, com o motivo de cada escolha ao lado dela.
contracts/
Superfície pública versionada
Cada export é função nomeada com contrato declarado. Mudar exige subir a versão da API.
internal/
Boot, chamada remota, banco e ciclo de vida
Inalcançável de fora. Mudar aqui não quebra consumidor nenhum.
shared/
Resultado, esquema, identificador de transação, limite, log, rastreio
Sem dependência de função da plataforma — testável num Lua comum.
config/
Configuração do operador
Fica fora do pacote fechado.
Por dentro da engenharia
Decisões de implementação
Um bloco por decisão de implementação. Abra o que interessa.
Benchmark identificou o limite no cliente do banco
Problema
A intuição de quem escreve servidor de jogo é que o banco de dados é o limite. Projetar em cima dessa intuição significa otimizar a coisa errada e descobrir o teto real no dia do evento cheio.
Solução
Um harness de benchmark rodou antes do framework. Quatro processos Node fizeram 6.460 leituras por segundo contra 2.254 de um só, com a CPU do banco em 0,04%. Como o driver usado pela plataforma é um único processo Node, o orçamento do servidor é o dele: cerca de 2.200 leituras e 1.100 transações por segundo.
Por que foi feito assim
Um orçamento conhecido antes transforma "isso aguenta?" em aritmética. Sem ele, a resposta é sempre uma opinião com sotaque de certeza.
Round-trip é o recurso escasso
Problema
Otimizar consulta é o reflexo natural. Mas o custo dominante não estava dentro do banco — estava no número de idas e voltas até ele. E encadear consultas dentro do jogo custa cerca de 4 ms cada, porque o caminho até o driver atravessa outra thread.
Solução
A mesma operação econômica em um único round-trip alcançou 2.900 operações por segundo, contra 818 da versão em transação explícita com quatro idas e voltas. O contrato passou a oferecer execução paralela de consultas independentes, e a operação de valor virou uma transação com dois comandos.
Por que foi feito assim
Consulta mais rápida melhora o que já está no lugar certo. O formato da operação decide se existe lugar certo. Consulta independente vai em paralelo, ou não vai.
Operações financeiras nomeadas e idempotentes
Problema
Uma função genérica de somar saldo é o ponto único por onde passa todo bug econômico de servidor de RP: qualquer chamador, qualquer motivo, nenhum rastro do porquê.
Solução
Toda mutação de valor é uma operação nomeada, com identificador de transação gerado pelo servidor e linha no razão. O identificador é ordenável no tempo, então inserções em sequência ficam próximas no índice de uma tabela que só cresce. O índice único sobre ele É a idempotência: repetir a mesma transação colide e falha limpo.
Por que foi feito assim
Um razão com operação nomeada responde "por que este jogador tem esse saldo" em uma consulta. E o modo de falha escolhido é o certo: perde uma operação legítima e registra, nunca duplica dinheiro.
Rate limiting por token bucket
Problema
Nas bases existentes qualquer cliente chama qualquer handler, na frequência que quiser, sem custo. Mas um limite por janela fixa recusa rajada legítima — abrir um inventário dispara várias chamadas juntas.
Solução
Balde de tokens: absorve a rajada e ainda assim limita a taxa sustentada. O tempo entra por parâmetro, sem depender de função da plataforma, o que torna a primitiva testável fora do servidor de jogo.
Por que foi feito assim
Defesa que atrapalha o uso normal é desligada pela própria equipe na primeira reclamação. A que absorve rajada sobrevive.
Decisões
Decisões e trade-offs
Cada decisão registra a justificativa e o trade-off aceito.
Lua, mesmo com o Node medido como mais rápido.
A vantagem do Node aparece em computação pura, de 2 a 12×, e o orçamento real é dominado por espera de banco e travessia de fronteira — centenas de nanossegundos dentro de milissegundos. Além disso, só arquivo Lua é protegível pelo mecanismo de escrow da plataforma, o que importa para distribuir o framework.
Custo aceito · Abre mão de desempenho em laços numéricos pesados. Se algum subsistema futuro for de fato limitado por CPU, a decisão precisa ser reaberta com número novo.
A API recebe lista e devolve tudo de uma vez; buscar um por um dentro de laço não existe.
Atravessar fronteira de resource foi medido em 38 microssegundos — 96 vezes uma chamada local. Por operação é aceitável; por quadro é impossível.
Custo aceito · Assinaturas menos convenientes para o caso de um item só. O custo ficou no tipo da função, e não num comentário pedindo cuidado.
Escrever "não medido" na documentação em vez de estimar.
Estimativa escrita com a mesma tipografia de um número medido vira número medido três meses depois, quando ninguém lembra a diferença.
Custo aceito · Deixa lacunas visíveis no documento e torna algumas decisões mais lentas, porque exigem medir antes.
Stack
O que foi usado
Runtime
- Lua 5.4
- CfxLua
- FXServer
- OneSync
Dados
- MariaDB
- Migrations SQL versionadas
- Consulta paralela
Verificação
- Lint que compila todo Lua num Lua real
- Testes de unidade
- Testes de integração com banco
- Checagem de manifesto