Andrade Systems
Em desenvolvimentoFiveM

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.

  1. 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.

  2. 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.

  3. 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