ESCOLHA SEU LADO DA TRETA
-Cada facção tem elenco, grito e jeito de brigar. Escolha o coro.
--
--
--
- -diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
new file mode 100644
index 000000000..9e1bce73a
--- /dev/null
+++ b/.github/workflows/autofix.yml
@@ -0,0 +1,242 @@
+# autofix — o bot conserta o MECÂNICO e deixa o PR pronto. Ele NÃO mergeia.
+#
+# POR QUE EXISTE (medido em 21/08/2026, nos últimos 29 PRs): nenhum bot deste
+# repositório jamais commitou um conserto. O #405 ficou vermelho por
+# `DOCS1: documentação DESATUALIZADA`, cujo conserto é `npm run docs` mais um
+# commit — trabalho sem julgamento nenhum, que hoje ou espera um mantenedor ou
+# devolve o PR para o autor.
+#
+# A TRAVA: `scripts/ci/autofix_allowlist.py`. O bot só commita se TUDO que ele
+# mexeu for arquivo gerado. Encostou em qualquer outro caminho, aborta e comenta.
+# Sem essa lista, o primeiro conserto errado reescreve o mapa de um colaborador e
+# ninguém repara — e a confiança de todo mundo de fora vai junto.
+#
+# `pull_request_target` porque é o único gatilho que enxerga secret em PR de fork
+# (ver a régua WSEC1). O checkout é do MERGE do PR e roda apenas ferramentas do
+# repositório base sobre ele — nunca `npm run` de script vindo do fork.
+name: autofix
+
+on:
+ pull_request_target:
+ types: [opened, reopened, synchronize, ready_for_review]
+ # A MAIN ANDANDO é o que desatualiza PR, não o PR se mexendo. Foram 9 `chore(release)`
+ # em 20 horas, e cada um reescreve README/STATUS/ARCH/docs — reabrindo conflito em TODO
+ # PR aberto ao mesmo tempo. Sem este gatilho o autofix só acorda quando o autor empurra,
+ # ou seja, nunca para quem está parado esperando revisão, que é justamente quem precisa.
+ push:
+ branches: [main]
+ workflow_dispatch:
+ inputs:
+ pr_number:
+ description: PR a consertar
+ required: true
+ type: number
+
+permissions:
+ contents: read
+ pull-requests: write
+
+concurrency:
+ group: autofix-${{ github.event.pull_request.number || inputs.pr_number || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ # Varredura pós-release: descobre quem ficou para trás e dispara o autofix de cada um
+ # pelo `workflow_dispatch`, que é o caminho que enxerga secret. Barato — quem já está
+ # em dia sai no primeiro passo do autofix, sem clonar nada pesado.
+ varredura:
+ if: github.event_name == 'push'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ env:
+ GH_TOKEN: ${{ secrets.CSBRASIL_BOT_TOKEN }}
+ REPO: ${{ github.repository }}
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - name: Dispara o autofix de quem ficou para trás
+ run: |
+ if [ -z "$GH_TOKEN" ]; then
+ echo "::warning::CSBRASIL_BOT_TOKEN ausente — varredura não roda, e não reprova ninguém."
+ exit 0
+ fi
+ gh pr list --repo "$REPO" --state open --base main \
+ --json number,mergeable,isDraft \
+ --jq '.[] | select(.isDraft | not) | select(.mergeable != "MERGEABLE") | .number' > /tmp/atrasados.txt
+ echo "PRs desatualizados: $(wc -l < /tmp/atrasados.txt)"
+ while read -r pr; do
+ [ -n "$pr" ] || continue
+ echo " → #$pr"
+ gh workflow run autofix.yml --repo "$REPO" -f pr_number="$pr" \
+ || echo "::warning::não deu para disparar o autofix do #$pr"
+ done < /tmp/atrasados.txt
+
+ autofix:
+ if: github.event_name != 'push' && github.event.pull_request.draft != true
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ env:
+ GH_TOKEN: ${{ secrets.CSBRASIL_BOT_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
+ REPO: ${{ github.repository }}
+ steps:
+ - name: Guard de identidade
+ run: |
+ if [ -z "$GH_TOKEN" ]; then
+ echo "::warning::CSBRASIL_BOT_TOKEN ausente — autofix não roda, e não reprova ninguém por isso."
+ echo "pular=1" >> "$GITHUB_ENV"
+ fi
+
+ - name: Dados do PR
+ if: env.pular != '1'
+ run: |
+ gh pr view "$PR_NUMBER" --repo "$REPO" \
+ --json headRefName,headRepository,headRepositoryOwner,maintainerCanModify > /tmp/pr.json
+ {
+ echo "HEAD_REF=$(jq -r .headRefName /tmp/pr.json)"
+ echo "HEAD_REPO=$(jq -r '.headRepositoryOwner.login + "/" + .headRepository.name' /tmp/pr.json)"
+ # `maintainerCanModify` é FALSE por definição quando o PR não vem de fork —
+ # a opção nem existe ali. Sem esta conta o bot nunca empurrava em PR de casa,
+ # que é justamente onde ele sempre pode (medido no #425).
+ echo "PODE_EDITAR=$(jq -r 'if (.headRepositoryOwner.login + "/" + .headRepository.name) == env.REPO then "true" else (.maintainerCanModify|tostring) end' /tmp/pr.json)"
+ } >> "$GITHUB_ENV"
+
+ # Checa o código do PR para poder REGERAR a doc a partir dele. Nada do fork é
+ # executado: os geradores abaixo são os do repositório base, restaurados logo
+ # depois do checkout.
+ - name: Checkout do PR
+ if: env.pular != '1'
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ repository: ${{ env.HEAD_REPO }}
+ ref: ${{ env.HEAD_REF }}
+ token: ${{ secrets.CSBRASIL_BOT_TOKEN }}
+ fetch-depth: 0
+
+ - name: Traz a base e resolve conflito de arquivo gerado
+ if: env.pular != '1'
+ id: merge
+ run: |
+ git config user.name "csbrasil-bot"
+ git config user.email "csbrasil-bot@users.noreply.github.com"
+ git remote add base "https://github.com/$REPO.git" 2>/dev/null || true
+ BASE="${{ github.event.pull_request.base.ref || 'main' }}"
+ git fetch base "$BASE"
+ # A mensagem vai EXPLÍCITA mesmo quando o merge não conflita: `--no-edit` usa a
+ # mensagem automática do git, que não leva `Agent:` nem `Signed-off-by` — e aí o
+ # bot conserta o PR e o dco reprova o commit que ele mesmo fez (medido no #406).
+ if git merge --no-edit -m "Merge da base (autofix)" \
+ -m "Agent: csbrasil-bot (autofix)" \
+ -m "Signed-off-by: csbrasil-bot Tamanho dos arquivos que o Total de Total de Bloco gerado por O número abaixo não é retórica, e não é escrito à mão: sai de 11 identidades de autoria humana assinam commit no histórico desta branch: 11 identidades de autoria humana assinam commit no histórico desta branch: Bloco gerado por Size of the files Total in Total in Block generated by The number below is not rhetoric, and it is not hand-written: it comes from 11 human author identities sign commits in this branch: 11 human author identities sign commits in this branch: Block generated by Os arq
gen-arch.mjs indexa — bloco gerado, regenerado por
npm run docs e conferido por npm run docs:check:
-Arquivo Linhas public/js/game.js6.838 public/js/main.js2.646 public/js/characters.js1.068 public/js/glbchars.js837 public/js/vmattach.js628 public/js/weapons.js344 public/js/springs.js260 public/js/: 31.744 linhas em 44 arquivos. O índice símbolo→linha, com a tabela de conflito, é outro bloco gerado: tools/eval/ARCH.md (npm run arch).
+Arquivo Linhas public/js/game.js6.910 public/js/main.js2.698 public/js/characters.js1.068 public/js/glbchars.js844 public/js/vmattach.js628 public/js/weapons.js346 public/js/springs.js260 public/js/: 32.001 linhas em 44 arquivos. O índice símbolo→linha, com a tabela de conflito, é outro bloco gerado: tools/eval/ARCH.md (npm run arch).
diff --git a/public/docs/assets/js/1be81749.06cc6d24.js b/public/docs/assets/js/1be81749.06cc6d24.js
new file mode 100644
index 000000000..0f72829c5
--- /dev/null
+++ b/public/docs/assets/js/1be81749.06cc6d24.js
@@ -0,0 +1 @@
+"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[568],{4208(e,s,r){r.r(s),r.d(s,{assets:()=>t,contentTitle:()=>c,default:()=>j,frontMatter:()=>i,metadata:()=>a,toc:()=>l});const a=JSON.parse('{"id":"comecando","title":"O que \xe9, e como rodar","description":"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo.","source":"@site/docs/comecando.md","sourceDirName":".","slug":"/","permalink":"/docs/","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/comecando.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"id":"comecando","title":"O que \xe9, e como rodar","sidebar_label":"Come\xe7ando","sidebar_position":1,"slug":"/","description":"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo."},"sidebar":"dev","next":{"title":"Stack e ferramentas","permalink":"/docs/stack"}}');var d=r(4848),o=r(8453),n=r(6025);const i={id:"comecando",title:"O que \xe9, e como rodar",sidebar_label:"Come\xe7ando",sidebar_position:1,slug:"/",description:"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo."},c="O que \xe9, e como rodar",t={},l=[{value:"Rodar em 3 comandos",id:"rodar-em-3-comandos",level:2},{value:"Linux, WebGL e modo compatibilidade",id:"linux-webgl-e-modo-compatibilidade",level:3},{value:"Alternativa sem Astro (zero depend\xeancia de build)",id:"alternativa-sem-astro-zero-depend\xeancia-de-build",level:3},{value:"A pegadinha que custa a primeira hora de todo mundo",id:"a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",level:2},{value:"Estrutura real do reposit\xf3rio",id:"estrutura-real-do-reposit\xf3rio",level:2},{value:"As duas zonas",id:"as-duas-zonas",level:3},{value:"Comandos que voc\xea vai usar",id:"comandos-que-voc\xea-vai-usar",level:2},{value:"Onde ir agora",id:"onde-ir-agora",level:2}];function h(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,o.R)(),...e.components};return(0,d.jsxs)(d.Fragment,{children:["\n",(0,d.jsx)("div",{className:"cs-hero",children:(0,d.jsx)("img",{className:"cs-hero__bird",src:(0,n.Ay)("/img/canarinho-header.webp"),alt:"CORO SOLTO: Treta Suprema \u2014 o canarinho, mascote do jogo, girando",width:"604",height:"240"})}),"\n",(0,d.jsx)(s.header,{children:(0,d.jsx)(s.h1,{id:"o-que-\xe9-e-como-rodar",children:"O que \xe9, e como rodar"})}),"\n",(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.strong,{children:"CORO SOLTO: Treta Suprema"})," (ex-CS BRASIL) \xe9 um FPS de navegador escrito em\nJavaScript vanilla sobre Three.js r160, no estilo do Counter-Strike 1.6: rounds,\nbots, AWP, placar por Tab, r\xe1dio de voz. Roda num link, sem instalar nada."]}),"\n",(0,d.jsxs)(s.p,{children:["Os n\xfameros abaixo ",(0,d.jsx)(s.strong,{children:"n\xe3o s\xe3o escritos \xe0 m\xe3o"}),": eles s\xe3o regerados por\n",(0,d.jsx)(s.code,{children:"node tools/gen-docs.mjs"})," a partir do c\xf3digo, e ",(0,d.jsx)(s.code,{children:"npm run docs:check"})," (dentro do\n",(0,d.jsx)(s.code,{children:"check:fast"}),") reprova o quality gate quando qualquer um deles diverge da \xe1rvore. Antes disso\nesta p\xe1gina envelhecia no primeiro commit \u2014 ver\n",(0,d.jsx)(s.a,{href:"/docs/arquitetura#o-que-%C3%A9-gerado-e-o-que-n%C3%A3o-%C3%A9",children:"o que \xe9 gerado, e o que n\xe3o \xe9"}),"."]}),"\n","\n",(0,d.jsxs)(s.table,{children:[(0,d.jsx)(s.thead,{children:(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.th,{children:"O que"}),(0,d.jsx)(s.th,{style:{textAlign:"right"},children:"Quanto"}),(0,d.jsx)(s.th,{children:"Onde confere"})]})}),(0,d.jsxs)(s.tbody,{children:[(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"C\xf3digo do jogo"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"32.001 linhas em 44 arquivos"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files public/js/*.js | xargs wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"game.js"})}),(0,d.jsxs)(s.td,{style:{textAlign:"right"},children:[(0,d.jsx)(s.strong,{children:"6.910"})," linhas"]}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"wc -l public/js/game.js"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"main.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"2.698 linhas"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"wc -l public/js/main.js"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Armas com GLB"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"26"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'public/models/weapons/*.glb' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"GLBs de personagem"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"45"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'public/models/characters/*.glb' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Props em GLB"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"108"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'public/models/props/*.glb' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Clipes de anima\xe7\xe3o versionados"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"573"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files public/models/anims | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Personagens jog\xe1veis"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"44, em 5 fac\xe7\xf5es"}),(0,d.jsxs)(s.td,{children:["array ",(0,d.jsx)(s.code,{children:"CHARACTERS"})," de ",(0,d.jsx)(s.code,{children:"characters.js"})]})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Mapas no registro"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"12"}),(0,d.jsxs)(s.td,{children:["objeto ",(0,d.jsx)(s.code,{children:"MAPS"})," de ",(0,d.jsx)(s.code,{children:"maps.js"})]})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Arn\xeases visuais em HTML"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"15"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'public/*.html' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Scripts do arn\xeas"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"200"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Scripts de pipeline"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"54"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'tools/*.mjs' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Tarefas de entrada escritas"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"26"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"git ls-files 'docs/issues/[0-9]*.md' | wc -l"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Vers\xe3o"}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:(0,d.jsx)(s.code,{children:"2.0.0-alpha.179"})}),(0,d.jsxs)(s.td,{children:[(0,d.jsx)(s.code,{children:"public/js/version.js"})," e ",(0,d.jsx)(s.code,{children:"package.json"})," (batem)"]})]})]})]}),"\n",(0,d.jsxs)(s.blockquote,{children:["\n",(0,d.jsxs)(s.p,{children:["Bloco gerado por ",(0,d.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,d.jsx)(s.code,{children:"o comando da coluna direita de cada linha"})]}),"\n"]}),"\n","\n",(0,d.jsxs)(s.p,{children:["E as regras de partida que mais mudam de lugar, todas lidas das constantes de\n",(0,d.jsx)(s.code,{children:"public/js/game.js"}),":"]}),"\n","\n",(0,d.jsxs)(s.table,{children:[(0,d.jsx)(s.thead,{children:(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.th,{children:"Regra"}),(0,d.jsx)(s.th,{children:"Valor"}),(0,d.jsx)(s.th,{children:"Constante"})]})}),(0,d.jsxs)(s.tbody,{children:[(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Fac\xe7\xf5es \xb7 personagens"}),(0,d.jsx)(s.td,{children:"5 \xb7 44 (B 9 \xb7 C 9 \xb7 E 8 \xb7 F 9 \xb7 U 9)"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"CHARACTERS"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Mapas no menu"}),(0,d.jsxs)(s.td,{children:["12 \u2014 2 abrem em rodadas, ",(0,d.jsx)(s.strong,{children:"10 em captura"})]}),(0,d.jsxs)(s.td,{children:[(0,d.jsx)(s.code,{children:"MAPS"})," / ",(0,d.jsx)(s.code,{children:"ctfMode"})]})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Respawn"}),(0,d.jsx)(s.td,{children:"2,2 s"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"RESPAWN_DELAY"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Round"}),(0,d.jsx)(s.td,{children:"99 s, 3 vit\xf3rias"}),(0,d.jsxs)(s.td,{children:[(0,d.jsx)(s.code,{children:"ROUND_TIME"})," / ",(0,d.jsx)(s.code,{children:"ROUNDS_TO_WIN"})]})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Captura"}),(0,d.jsxs)(s.td,{children:["alvo = ",(0,d.jsx)(s.strong,{children:"todas as bandeiras do mapa"}),", 2 rodadas (rede de seguran\xe7a 480 s)"]}),(0,d.jsxs)(s.td,{children:[(0,d.jsx)(s.code,{children:"capsToWin = ctfPts.length"})," / ",(0,d.jsx)(s.code,{children:"CTF_ROUNDS_TO_WIN"})]})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:"Regenera\xe7\xe3o de vida"}),(0,d.jsx)(s.td,{children:(0,d.jsxs)(s.strong,{children:["DESLIGADA \u2014 ",(0,d.jsx)(s.code,{children:"?regen=1"})," religa"]})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"REGEN"})})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsxs)(s.td,{children:["Ranking / p\xe1ginas ",(0,d.jsx)(s.code,{children:"/u/"})]}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"DESLIGADOS \u2014 \xe9 uma flag, volta numa linha"})}),(0,d.jsxs)(s.td,{children:[(0,d.jsx)(s.code,{children:"RANKING_ON"})," em ",(0,d.jsx)(s.code,{children:"src/lib/site.ts"})]})]})]})]}),"\n",(0,d.jsxs)(s.blockquote,{children:["\n",(0,d.jsxs)(s.p,{children:["Bloco gerado por ",(0,d.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,d.jsx)(s.code,{children:"constantes de public/js/game.js \xb7 RANKING_ON de src/lib/site.ts"})]}),"\n"]}),"\n","\n",(0,d.jsxs)(s.p,{children:["O menu aceita de ",(0,d.jsx)(s.strong,{children:"2\xd72 a 8\xd78"})," bots (o motor aceita de 1 a 8 por lado); o padr\xe3o \xe9 4\xd74."]}),"\n",(0,d.jsxs)(s.admonition,{title:"Dois desses s\xe3o escolha recente, n\xe3o defeito",type:"note",children:[(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.strong,{children:"A regenera\xe7\xe3o de vida foi desligada"})," em 05/08 (",(0,d.jsx)(s.code,{children:"REGEN = QS.get('regen') === '1'"}),"). Ela\nexistia, estilo CoD \u2014 6 s sem tomar dano e 22 HP/s \u2014, e o dono a reportou como bug\n(",(0,d.jsx)(s.em,{children:'"a vida do 1st player volta a 100, n\xe3o sei porque"'}),") justamente porque era ",(0,d.jsx)(s.strong,{children:"invis\xedvel"}),":\nsem \xedcone, sem som, sem linha nas configura\xe7\xf5es. Regra que o jogador n\xe3o percebe \xe9\nindistingu\xedvel de defeito. Ela continua inteira atr\xe1s de ",(0,d.jsx)(s.code,{children:"?regen=1"}),", com a simetria\njogador\u2194bot. ",(0,d.jsx)(s.strong,{children:"Quem religar tem que entregar o feedback junto"})," \u2014 e resolver o que ela\nvinha tapando: sem cura, kit ou colete, cada vida depois do primeiro contato j\xe1 estava\nperdida."]}),(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.strong,{children:"O ranking foi desligado"})," e trocado por telemetria an\xf4nima. ",(0,d.jsx)(s.code,{children:"/ranking"})," e ",(0,d.jsx)(s.code,{children:"/u/*"}),"\nrespondem ",(0,d.jsxs)(s.strong,{children:["200 com aviso + ",(0,d.jsx)(s.code,{children:"noindex"})]})," (n\xe3o 404 \u2014 as URLs est\xe3o indexadas e v\xe3o voltar),\ne ",(0,d.jsx)(s.code,{children:"/api/leaderboard"})," responde ",(0,d.jsx)(s.code,{children:"{disabled:true}"}),"."]})]}),"\n",(0,d.jsxs)(s.admonition,{title:"O quality gate N\xc3O est\xe1 verde, e isso \xe9 declarado",type:"caution",children:[(0,d.jsxs)(s.p,{children:["Quantas invariantes passam ",(0,d.jsx)(s.strong,{children:"n\xe3o \xe9 deriv\xe1vel do c\xf3digo"})," \u2014 \xe9 o resultado de uma execu\xe7\xe3o,\ne depende at\xe9 de qual insumo existe na m\xe1quina. Por isso esse placar n\xe3o \xe9 repetido aqui:\nele mora no cabe\xe7alho de\n",(0,d.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,d.jsx)(s.code,{children:"KNOWN-BUGS.md"})}),", colado\nde uma execu\xe7\xe3o real, com a lista das vermelhas, causa raiz e ",(0,d.jsx)(s.code,{children:"arquivo:linha"})," de cada uma.\n\xc9 esse arquivo que \xe9 mantido dia a dia."]}),(0,d.jsx)(s.p,{children:"Para o estado de hoje, rode \u2014 n\xe3o repita n\xfamero de cabe\xe7a:"}),(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{className:"language-bash",children:"npm run eval:vm && node tools/eval/invariants.mjs --json # 10-12 min\n"})}),(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.strong,{children:"A ordem importa"}),": invariante de viewmodel medida com o JSON de ontem inventa vermelha\n(ver ",(0,d.jsx)(s.a,{href:"/docs/colaborar#rodar-o-quality-gate",children:"Como colaborar"}),")."]})]}),"\n",(0,d.jsx)(s.h2,{id:"rodar-em-3-comandos",children:"Rodar em 3 comandos"}),"\n",(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # abre em http://localhost:4321 \u2014 essa p\xe1gina J\xc1 \xc9 o jogo\n"})}),"\n",(0,d.jsxs)(s.p,{children:["O pacote de \xe1udio (",(0,d.jsx)(s.code,{children:"npm run fetch-audio"}),") \xe9 ",(0,d.jsx)(s.strong,{children:"opcional"}),": sem ele o jogo usa sons\nsintetizados. A pasta ",(0,d.jsx)(s.code,{children:"public/audio/"})," n\xe3o \xe9 versionada."]}),"\n",(0,d.jsx)(s.h3,{id:"linux-webgl-e-modo-compatibilidade",children:"Linux, WebGL e modo compatibilidade"}),"\n",(0,d.jsx)(s.p,{children:"O jogo tenta WebGL2 e WebGL1, come\xe7ando pela escolha padr\xe3o do navegador e reduzindo\nantialias, prefer\xeancia de GPU e stencil antes de desistir. Quando cai em WebGL1,\nllvmpipe/SwiftShader ou outro degrau reduzido, ativa qualidade baixa apenas naquela\nsess\xe3o: DPR 0,75, sem bloom/sombras e com retratos est\xe1ticos na sele\xe7\xe3o."}),"\n",(0,d.jsxs)(s.p,{children:["Use ",(0,d.jsx)(s.code,{children:"?safe=1"})," para priorizar WebGL1 e o caminho de menor custo. Se nem esse modo abrir,\nconfira ",(0,d.jsx)(s.code,{children:"chrome://gpu"})," ou a se\xe7\xe3o Graphics de ",(0,d.jsx)(s.code,{children:"about:support"}),", ligue acelera\xe7\xe3o por\nhardware e atualize Mesa/driver pelo gerenciador da distribui\xe7\xe3o. Uma p\xe1gina n\xe3o pode\nfor\xe7ar um driver quando o navegador recusa criar at\xe9 o contexto WebGL1."]}),"\n",(0,d.jsx)(s.h3,{id:"alternativa-sem-astro-zero-depend\xeancia-de-build",children:"Alternativa sem Astro (zero depend\xeancia de build)"}),"\n",(0,d.jsxs)(s.p,{children:["O arn\xeas de avalia\xe7\xe3o traz um servidor est\xe1tico de 24 linhas que serve ",(0,d.jsx)(s.code,{children:"public/"})," e\nmapeia ",(0,d.jsx)(s.code,{children:"/"})," para o fonte da p\xe1gina do jogo:"]}),"\n",(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{className:"language-bash",children:"node tools/eval/serve.mjs 8123 # http://localhost:8123\n"})}),"\n",(0,d.jsxs)(s.p,{children:["Ele existe exatamente porque ",(0,d.jsx)(s.code,{children:"src/pages/index.astro"})," \xe9 HTML puro \u2014 d\xe1 pra servir o\narquivo cru sem passar pelo Astro (",(0,d.jsx)(s.code,{children:"tools/eval/serve.mjs:15"}),")."]}),"\n",(0,d.jsx)(s.h2,{id:"a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",children:"A pegadinha que custa a primeira hora de todo mundo"}),"\n",(0,d.jsxs)(s.p,{children:[(0,d.jsxs)(s.strong,{children:["N\xe3o existe ",(0,d.jsx)(s.code,{children:"public/index.html"}),"."]})," Servir a pasta ",(0,d.jsx)(s.code,{children:"public/"})," estaticamente te d\xe1 um\n\xedndice de diret\xf3rio com ",(0,d.jsx)(s.code,{children:"eval.html"}),", ",(0,d.jsx)(s.code,{children:"mapview.html"})," e companhia \u2014 nenhum deles \xe9 o jogo.\nO HTML do jogo \xe9 ",(0,d.jsx)(s.code,{children:"src/pages/index.astro"}),", servido na ",(0,d.jsx)(s.strong,{children:"rota raiz"})," pelo Astro. N\xe3o h\xe1\nrota ",(0,d.jsx)(s.code,{children:"/game"}),"."]}),"\n",(0,d.jsxs)(s.p,{children:["A confirma\xe7\xe3o independente est\xe1 no pr\xf3prio arn\xeas: ",(0,d.jsx)(s.code,{children:"tools/eval/serve.mjs:15"})," precisa de um\ncaso especial ",(0,d.jsx)(s.code,{children:"if (p === '/')"})," que l\xea ",(0,d.jsx)(s.code,{children:"src/pages/index.astro"})," do disco, justamente porque\nn\xe3o h\xe1 ",(0,d.jsx)(s.code,{children:"index.html"})," em ",(0,d.jsx)(s.code,{children:"public/"})," pra servir."]}),"\n",(0,d.jsx)(s.admonition,{title:"Esta se\xe7\xe3o j\xe1 foi uma lista de erros do README",type:"note",children:(0,d.jsxs)(s.p,{children:["At\xe9 04/08/2026 ela existia porque o ",(0,d.jsx)(s.code,{children:"README.md"})," da raiz mandava rodar\n",(0,d.jsx)(s.code,{children:"cd public && python3 -m http.server"}),' e falava num "jogo em ',(0,d.jsx)(s.code,{children:"/game/"}),'". As duas linhas\nforam corrigidas \u2014 o README hoje diz o certo. O que sobrou \xe9 o fato em si, que continua\nsendo a primeira pedra no caminho de quem chega.']})}),"\n",(0,d.jsx)(s.h2,{id:"estrutura-real-do-reposit\xf3rio",children:"Estrutura real do reposit\xf3rio"}),"\n",(0,d.jsx)(s.p,{children:"Duas zonas de c\xf3digo e uma terceira zona que \xe9 a raz\xe3o desta doc existir (o arn\xeas):"}),"\n",(0,d.jsxs)(s.p,{children:["Nenhuma contagem aqui: a \xe1rvore diz ",(0,d.jsx)(s.strong,{children:"o que \xe9 cada coisa"}),", e os n\xfameros vivem na tabela\ngerada l\xe1 em cima. Misturar os dois \xe9 como o ",(0,d.jsx)(s.code,{children:"ARCH.md"})," escrito \xe0 m\xe3o nasceu errado."]}),"\n",(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{children:'public/ O JOGO \u2014 vanilla ES modules, ZERO build\n js/\n game.js a classe Game (loop, bots, tiro, HUD) \u2014 o maior arquivo do repo\n main.js menu, wiring de DOM, persist\xeancia\n vmattach.js springs.js weapons.js fparms.js handik.js viewmodel/armas\n maps.js o REGISTRO de mapas (quem n\xe3o est\xe1 aqui n\xe3o \xe9 jog\xe1vel)\n map_brasilia.js map_piscina.js map_havan.js\n map_ferrovelho.js map_quebrada.js os mapas registrados\n map_piscinao_ramos.js "Piscin\xe3o" \u2014 existe no disco, FORA do registro\n mapprops.js map_decals.js props e grafite\n bloom.js textures.js vao.js stylize.js gpuparticles.js gr\xe1ficos/FX\n characters.js glbchars.js personagens\n audio.js version.js site-bg.js\n models/ armas, personagens, props e clipes de anima\xe7\xe3o em GLB\n vendor/ Three.js vendorizado (sem CDN, sem npm no runtime)\n style.css o HUD inteiro\n *.html arn\xeases visuais (eval, mapview, weapontest, vm-inspect\u2026)\n\nsrc/ O SITE (Astro + adapter Vercel)\n pages/index.astro \u26a0 ISTO \xc9 O JOGO (HTML + import map + HUD)\n pages/sobre.astro landing/FAQ com JSON-LD\n pages/personagens.astro como-jogar.astro ranking.astro mapa.astro\n pages/u/[...path].astro perfil p\xfablico\n pages/api/*.ts SSR: leaderboard, submit-match, register, badge, avatar\n layouts/Layout.astro shell do site (n\xe3o do jogo)\n lib/ supabase, svg, geo, fmt\n\ntools/\n eval/ O ARN\xcaS \u2014 r\xe9guas, quality gate e sondas. Ver "Quality gates"\n invariants.mjs o quality gate\n ref-measure.py mede os frames de refer\xeancia (a doutrina da casa)\n harness.mjs sobe o Game real em node com DOM stubado\n ARCH.md BAR.md mapa de conflito (gerado) e a r\xe9gua visual\n gen-arch.mjs gera e VALIDA o ARCH.md\n gen-docs.mjs gera e VALIDA os blocos num\xe9ricos desta documenta\xe7\xe3o\n gen-asset.mjs gera prop 3D por texto (Tripo/Meshy)\n gen-image.mjs gera arte 2D por texto (OpenRouter)\n\n (banco: schema/migrations s\xe3o PRIVADOS \u2014 fora do repo)\n.github/workflows/ci.yml o quality gate rodando em CI\n'})}),"\n",(0,d.jsx)(s.p,{children:"Os mapas registrados hoje, e em que modo cada um abre:"}),"\n","\n",(0,d.jsxs)(s.table,{children:[(0,d.jsx)(s.thead,{children:(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.th,{children:"Id"}),(0,d.jsx)(s.th,{children:"Nome no menu"}),(0,d.jsx)(s.th,{children:"Abre em"}),(0,d.jsxs)(s.th,{children:["Arquivo em ",(0,d.jsx)(s.code,{children:"public/js/"})]}),(0,d.jsx)(s.th,{style:{textAlign:"right"},children:"Linhas"})]})}),(0,d.jsxs)(s.tbody,{children:[(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"praca_poderes"})}),(0,d.jsx)(s.td,{children:"Pra\xe7a dos Tr\xeas Poderes"}),(0,d.jsx)(s.td,{children:"rodadas"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_brasilia.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"1.830"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"piscina_treta"})}),(0,d.jsx)(s.td,{children:"Piscina da Treta"}),(0,d.jsx)(s.td,{children:"rodadas"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_piscina.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"810"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"loja_h"})}),(0,d.jsx)(s.td,{children:"Loja H (Estacionamento)"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_havan.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"1.964"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"ferro_velho"})}),(0,d.jsx)(s.td,{children:"Ferro Velho do Z\xe9"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_ferrovelho.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"1.888"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"quebrada"})}),(0,d.jsx)(s.td,{children:"Quebrada (Rua do Baile)"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_quebrada.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"1.599"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"posto_treta"})}),(0,d.jsx)(s.td,{children:"Posto da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_posto.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"489"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"upa_24h"})}),(0,d.jsx)(s.td,{children:"UPA 24h da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_upa.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"288"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"obras_prefeitura"})}),(0,d.jsx)(s.td,{children:"Obras da Prefeitura"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_obras.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"240"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"atacadao_treta"})}),(0,d.jsx)(s.td,{children:"Atacad\xe3o da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_atacadao.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"255"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"parque_treta"})}),(0,d.jsx)(s.td,{children:"Parque da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_parque.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"402"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"velho_oeste"})}),(0,d.jsx)(s.td,{children:"Velho Oeste da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_velho_oeste.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"433"})]}),(0,d.jsxs)(s.tr,{children:[(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"penitenciaria"})}),(0,d.jsx)(s.td,{children:"Penitenci\xe1ria da Treta"}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.strong,{children:"captura"})}),(0,d.jsx)(s.td,{children:(0,d.jsx)(s.code,{children:"map_penitenciaria.js"})}),(0,d.jsx)(s.td,{style:{textAlign:"right"},children:"247"})]})]})]}),"\n",(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.strong,{children:"12 mapas registrados"})," \u2014 2 abrem em rodadas e 10 em captura. ",(0,d.jsx)(s.code,{children:"ctfMode"})," ",(0,d.jsx)(s.strong,{children:"abre"})," o mapa em captura, n\xe3o prende: o jogador troca no menu (\xe9 a ",(0,d.jsx)(s.code,{children:"MOD1"}),"). H\xe1 14 arquivos ",(0,d.jsx)(s.code,{children:"map_*.js"})," em ",(0,d.jsx)(s.code,{children:"public/js/"})," \u2014 arquivo no disco ",(0,d.jsx)(s.strong,{children:"n\xe3o"})," implica mapa jog\xe1vel."]}),"\n",(0,d.jsxs)(s.blockquote,{children:["\n",(0,d.jsxs)(s.p,{children:["Bloco gerado por ",(0,d.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,d.jsx)(s.code,{children:"objeto MAPS de public/js/maps.js"})]}),"\n"]}),"\n","\n",(0,d.jsx)(s.h3,{id:"as-duas-zonas",children:"As duas zonas"}),"\n",(0,d.jsxs)(s.p,{children:["Em uma linha cada: ",(0,d.jsxs)(s.strong,{children:[(0,d.jsx)(s.code,{children:"public/"})," \xe9 o jogo"]})," (vanilla, ES modules, sem framework e sem\nbundler) e ",(0,d.jsxs)(s.strong,{children:[(0,d.jsx)(s.code,{children:"src/"})," \xe9 o site"]})," (Astro com SSR, onde framework \xe9 bem-vindo). O que cada\nregra da fronteira paga, e por que ela \xe9 dura, est\xe1 em\n",(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/stack#as-duas-zonas-e-por-que-a-fronteira-%C3%A9-dura",children:"Stack e ferramentas"})})," \u2014 uma\np\xe1gina s\xf3, para n\xe3o haver duas vers\xf5es da mesma fronteira."]}),"\n",(0,d.jsxs)(s.p,{children:["O que voc\xea precisa saber ",(0,d.jsx)(s.strong,{children:"antes de editar"})," \xe9 a consequ\xeancia: o jogo \xe9 carregado pela\np\xe1gina Astro via ",(0,d.jsx)(s.strong,{children:"import map com vers\xe3o e hash do conte\xfado"})," (",(0,d.jsx)(s.code,{children:"src/pages/index.astro"}),")."]}),"\n",(0,d.jsx)(s.admonition,{title:"Preserve o manifesto publicado",type:"danger",children:(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.code,{children:"scripts/module-cache.mjs"})," deriva o hash dos m\xf3dulos publicados sob ",(0,d.jsx)(s.code,{children:"public/js/"})," e o import map\naplica essa revis\xe3o ao grafo inteiro. N\xe3o fa\xe7a bump manual e n\xe3o inclua bancadas que\n",(0,d.jsx)(s.code,{children:"scripts/prune-dist.mjs"})," remove. ",(0,d.jsx)(s.code,{children:"npm run eval:shaderbudget"})," (SB7) confere as duas propriedades."]})}),"\n",(0,d.jsx)(s.h2,{id:"comandos-que-voc\xea-vai-usar",children:"Comandos que voc\xea vai usar"}),"\n",(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{className:"language-bash",children:"npm run dev # site + jogo (Astro, :4321) \u2014 a rota / J\xc1 \xc9 o jogo\nnpm run build # dist/client + dist/server\nnpm run eval:vm # enquadramento do viewmodel \u2014 RODE ANTES das invariantes\nnpm run eval:invariants # as invariantes \u2014 node puro, 10-12 min\nnpm run eval:bots # botsim 60 s por mapa, sementes fixas\nnpm run eval:mat # material/luz/fog/textura nos mapas\nnpm run docs # regenera os blocos num\xe9ricos desta documenta\xe7\xe3o\nnode tools/eval/serve.mjs 8123 # servidor est\xe1tico sem Astro\n"})}),"\n",(0,d.jsxs)(s.p,{children:["E os dois quality gates, com a lista exata do que cada um roda \u2014 direto do ",(0,d.jsx)(s.code,{children:"package.json"}),":"]}),"\n","\n",(0,d.jsx)(s.pre,{children:(0,d.jsx)(s.code,{className:"language-bash",children:"npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:mutcega eval:autofix eval:deploygate eval:portaointeiro eval:wfsecret eval:comentario eval:fixture eval:preload eval:docsautoria eval:replaycam\n"})}),"\n",(0,d.jsxs)(s.p,{children:[(0,d.jsx)(s.code,{children:"package.json"})," tem ",(0,d.jsx)(s.strong,{children:"124 scripts"}),"; o motivo de cada um mora em ",(0,d.jsx)(s.code,{children:"SCRIPTS.md"})," (migrado das chaves ",(0,d.jsx)(s.code,{children:"//nome"})," em 18/08/2026) \u2014 \xe9 onde est\xe1 o porqu\xea."]}),"\n",(0,d.jsxs)(s.blockquote,{children:["\n",(0,d.jsxs)(s.p,{children:["Bloco gerado por ",(0,d.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,d.jsx)(s.code,{children:"node -p \"Object.keys(require('./package.json').scripts)\""})]}),"\n"]}),"\n","\n",(0,d.jsxs)(s.p,{children:["O ",(0,d.jsx)(s.code,{children:"check:fast"})," cobre as r\xe9guas de node puro; o CI (",(0,d.jsx)(s.code,{children:".github/workflows/ci.yml"}),") roda o\nmesmo conjunto mais os passos que exigem rede."]}),"\n",(0,d.jsxs)(s.admonition,{type:"tip",children:[(0,d.jsxs)(s.mdxAdmonitionTitle,{children:["Use o ",(0,d.jsx)(s.code,{children:"check:fast"})," no loop, o ",(0,d.jsx)(s.code,{children:"portao-browser"})," antes do PR"]}),(0,d.jsxs)(s.p,{children:["O ",(0,d.jsx)(s.code,{children:"portao-browser"})," gasta 10-12 min porque sobe o jogo cinco vezes. O ",(0,d.jsx)(s.code,{children:"check:fast"})," cobre as r\xe9guas\nque nasceram dos bugs mais recentes (menu de pausa, rodada de captura, regenera\xe7\xe3o,\nmanifesto de anima\xe7\xe3o) e roda em cerca de um minuto."]})]}),"\n",(0,d.jsx)(s.h2,{id:"onde-ir-agora",children:"Onde ir agora"}),"\n",(0,d.jsxs)(s.p,{children:["A ordem da barra lateral ",(0,d.jsx)(s.strong,{children:"\xe9"})," a ordem de leitura, e cada p\xe1gina entrega uma coisa:"]}),"\n",(0,d.jsxs)(s.ol,{children:["\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/stack",children:"Stack e ferramentas"})})," \u2014 com o que isso \xe9 feito, com a vers\xe3o declarada\nde cada pe\xe7a. \xc9 onde a fronteira ",(0,d.jsx)(s.code,{children:"public/"})," \xd7 ",(0,d.jsx)(s.code,{children:"src/"})," est\xe1 explicada por inteiro."]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/instrumentacao-ai",children:"Instrumenta\xe7\xe3o de IA"})})," \u2014 como o trabalho \xe9 feito aqui. Se\nvoc\xea nunca colaborou com agentes num reposit\xf3rio, comece por essa."]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/quality-gates",children:"O quality gate"})})," \u2014 o que \xe9 uma invariante, como se escreve uma, as\nduas leis da casa e o teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua. ",(0,d.jsx)(s.strong,{children:"\xc9 a p\xe1gina mais \xfatil do\nsite."})]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/arquitetura",children:"Arquitetura"})})," \u2014 como N agentes editam o mesmo arquivo sem\ncolidir, e a tabela de conflito. Leia antes de tocar em ",(0,d.jsx)(s.code,{children:"game.js"}),"."]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/colaborar",children:"Como colaborar"})})," \u2014 o que um PR precisa pra entrar, e as ",(0,d.jsx)(s.strong,{children:"tarefas\nde primeira contribui\xe7\xe3o"})," j\xe1 escritas em\n",(0,d.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/tree/main/docs/issues",children:(0,d.jsx)(s.code,{children:"docs/issues/"})})," (com um\n",(0,d.jsx)(s.code,{children:"abrir-issues.sh"})," pronto \u2014 elas ainda n\xe3o foram abertas no GitHub)."]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:"Licen\xe7a"})," \u2014 o ",(0,d.jsx)(s.code,{children:"LICENSE"})," na raiz declara (hoje AGPL-3.0); as superf\xedcies que\nrepetem o nome e mudam junto est\xe3o no ",(0,d.jsx)(s.code,{children:"CONTRIBUTING.md"}),"."]}),"\n",(0,d.jsxs)(s.li,{children:[(0,d.jsx)(s.strong,{children:(0,d.jsx)(s.a,{href:"/docs/estado",children:"Estado atual"})})," \u2014 fontes vivas de produ\xe7\xe3o, dados e d\xedvida conhecida\ndesde a \xfaltima medi\xe7\xe3o colada."]}),"\n"]}),"\n",(0,d.jsxs)(s.p,{children:["Para onde o projeto ",(0,d.jsx)(s.strong,{children:"vai"})," n\xe3o est\xe1 nesta documenta\xe7\xe3o: \xe9 o\n",(0,d.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,d.jsx)(s.code,{children:"docs/ROADMAP.md"})}),", e o\nplano execut\xe1vel \xe9 o\n",(0,d.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/plans/08-RELEASE-PROFISSIONAL.md",children:(0,d.jsx)(s.code,{children:"plans/08"})}),"."]})]})}function j(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,d.jsx)(s,{...e,children:(0,d.jsx)(h,{...e})}):h(e)}},8453(e,s,r){r.d(s,{R:()=>n,x:()=>i});var a=r(6540);const d={},o=a.createContext(d);function n(e){const s=a.useContext(o);return a.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function i(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(d):e.components||d:n(e.components),a.createElement(o.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/assets/js/1be81749.cc5d4388.js b/public/docs/assets/js/1be81749.cc5d4388.js
deleted file mode 100644
index 130fa3673..000000000
--- a/public/docs/assets/js/1be81749.cc5d4388.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[568],{4208(e,s,r){r.r(s),r.d(s,{assets:()=>t,contentTitle:()=>c,default:()=>j,frontMatter:()=>i,metadata:()=>d,toc:()=>l});const d=JSON.parse('{"id":"comecando","title":"O que \xe9, e como rodar","description":"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo.","source":"@site/docs/comecando.md","sourceDirName":".","slug":"/","permalink":"/docs/","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/comecando.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"id":"comecando","title":"O que \xe9, e como rodar","sidebar_label":"Come\xe7ando","sidebar_position":1,"slug":"/","description":"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo."},"sidebar":"dev","next":{"title":"Stack e ferramentas","permalink":"/docs/stack"}}');var a=r(4848),n=r(8453),o=r(6025);const i={id:"comecando",title:"O que \xe9, e como rodar",sidebar_label:"Come\xe7ando",sidebar_position:1,slug:"/",description:"O que \xe9 o CORO SOLTO, como rodar em 3 comandos e a estrutura real do reposit\xf3rio \u2014 conferida contra o c\xf3digo."},c="O que \xe9, e como rodar",t={},l=[{value:"Rodar em 3 comandos",id:"rodar-em-3-comandos",level:2},{value:"Linux, WebGL e modo compatibilidade",id:"linux-webgl-e-modo-compatibilidade",level:3},{value:"Alternativa sem Astro (zero depend\xeancia de build)",id:"alternativa-sem-astro-zero-depend\xeancia-de-build",level:3},{value:"A pegadinha que custa a primeira hora de todo mundo",id:"a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",level:2},{value:"Estrutura real do reposit\xf3rio",id:"estrutura-real-do-reposit\xf3rio",level:2},{value:"As duas zonas",id:"as-duas-zonas",level:3},{value:"Comandos que voc\xea vai usar",id:"comandos-que-voc\xea-vai-usar",level:2},{value:"Onde ir agora",id:"onde-ir-agora",level:2}];function h(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,n.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:["\n",(0,a.jsx)("div",{className:"cs-hero",children:(0,a.jsx)("img",{className:"cs-hero__bird",src:(0,o.Ay)("/img/canarinho-header.webp"),alt:"CORO SOLTO: Treta Suprema \u2014 o canarinho, mascote do jogo, girando",width:"604",height:"240"})}),"\n",(0,a.jsx)(s.header,{children:(0,a.jsx)(s.h1,{id:"o-que-\xe9-e-como-rodar",children:"O que \xe9, e como rodar"})}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"CORO SOLTO: Treta Suprema"})," (ex-CS BRASIL) \xe9 um FPS de navegador escrito em\nJavaScript vanilla sobre Three.js r160, no estilo do Counter-Strike 1.6: rounds,\nbots, AWP, placar por Tab, r\xe1dio de voz. Roda num link, sem instalar nada."]}),"\n",(0,a.jsxs)(s.p,{children:["Os n\xfameros abaixo ",(0,a.jsx)(s.strong,{children:"n\xe3o s\xe3o escritos \xe0 m\xe3o"}),": eles s\xe3o regerados por\n",(0,a.jsx)(s.code,{children:"node tools/gen-docs.mjs"})," a partir do c\xf3digo, e ",(0,a.jsx)(s.code,{children:"npm run docs:check"})," (dentro do\n",(0,a.jsx)(s.code,{children:"check:fast"}),") reprova o quality gate quando qualquer um deles diverge da \xe1rvore. Antes disso\nesta p\xe1gina envelhecia no primeiro commit \u2014 ver\n",(0,a.jsx)(s.a,{href:"/docs/arquitetura#o-que-%C3%A9-gerado-e-o-que-n%C3%A3o-%C3%A9",children:"o que \xe9 gerado, e o que n\xe3o \xe9"}),"."]}),"\n","\n",(0,a.jsxs)(s.table,{children:[(0,a.jsx)(s.thead,{children:(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.th,{children:"O que"}),(0,a.jsx)(s.th,{style:{textAlign:"right"},children:"Quanto"}),(0,a.jsx)(s.th,{children:"Onde confere"})]})}),(0,a.jsxs)(s.tbody,{children:[(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"C\xf3digo do jogo"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"31.744 linhas em 44 arquivos"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files public/js/*.js | xargs wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"game.js"})}),(0,a.jsxs)(s.td,{style:{textAlign:"right"},children:[(0,a.jsx)(s.strong,{children:"6.838"})," linhas"]}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"wc -l public/js/game.js"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"main.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"2.646 linhas"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"wc -l public/js/main.js"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Armas com GLB"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"26"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'public/models/weapons/*.glb' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"GLBs de personagem"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"45"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'public/models/characters/*.glb' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Props em GLB"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"108"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'public/models/props/*.glb' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Clipes de anima\xe7\xe3o versionados"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"573"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files public/models/anims | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Personagens jog\xe1veis"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"44, em 5 fac\xe7\xf5es"}),(0,a.jsxs)(s.td,{children:["array ",(0,a.jsx)(s.code,{children:"CHARACTERS"})," de ",(0,a.jsx)(s.code,{children:"characters.js"})]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Mapas no registro"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"12"}),(0,a.jsxs)(s.td,{children:["objeto ",(0,a.jsx)(s.code,{children:"MAPS"})," de ",(0,a.jsx)(s.code,{children:"maps.js"})]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Arn\xeases visuais em HTML"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"15"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'public/*.html' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Scripts do arn\xeas"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"192"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Scripts de pipeline"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"54"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'tools/*.mjs' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Tarefas de entrada escritas"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"26"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"git ls-files 'docs/issues/[0-9]*.md' | wc -l"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Vers\xe3o"}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:(0,a.jsx)(s.code,{children:"2.0.0-alpha.169"})}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"public/js/version.js"})," e ",(0,a.jsx)(s.code,{children:"package.json"})," (batem)"]})]})]})]}),"\n",(0,a.jsxs)(s.blockquote,{children:["\n",(0,a.jsxs)(s.p,{children:["Bloco gerado por ",(0,a.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,a.jsx)(s.code,{children:"o comando da coluna direita de cada linha"})]}),"\n"]}),"\n","\n",(0,a.jsxs)(s.p,{children:["E as regras de partida que mais mudam de lugar, todas lidas das constantes de\n",(0,a.jsx)(s.code,{children:"public/js/game.js"}),":"]}),"\n","\n",(0,a.jsxs)(s.table,{children:[(0,a.jsx)(s.thead,{children:(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.th,{children:"Regra"}),(0,a.jsx)(s.th,{children:"Valor"}),(0,a.jsx)(s.th,{children:"Constante"})]})}),(0,a.jsxs)(s.tbody,{children:[(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Fac\xe7\xf5es \xb7 personagens"}),(0,a.jsx)(s.td,{children:"5 \xb7 44 (B 9 \xb7 C 9 \xb7 E 8 \xb7 F 9 \xb7 U 9)"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"CHARACTERS"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Mapas no menu"}),(0,a.jsxs)(s.td,{children:["12 \u2014 2 abrem em rodadas, ",(0,a.jsx)(s.strong,{children:"10 em captura"})]}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"MAPS"})," / ",(0,a.jsx)(s.code,{children:"ctfMode"})]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Respawn"}),(0,a.jsx)(s.td,{children:"2,2 s"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"RESPAWN_DELAY"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Round"}),(0,a.jsx)(s.td,{children:"99 s, 3 vit\xf3rias"}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"ROUND_TIME"})," / ",(0,a.jsx)(s.code,{children:"ROUNDS_TO_WIN"})]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Captura"}),(0,a.jsxs)(s.td,{children:["alvo = ",(0,a.jsx)(s.strong,{children:"todas as bandeiras do mapa"}),", 2 rodadas (rede de seguran\xe7a 480 s)"]}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"capsToWin = ctfPts.length"})," / ",(0,a.jsx)(s.code,{children:"CTF_ROUNDS_TO_WIN"})]})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:"Regenera\xe7\xe3o de vida"}),(0,a.jsx)(s.td,{children:(0,a.jsxs)(s.strong,{children:["DESLIGADA \u2014 ",(0,a.jsx)(s.code,{children:"?regen=1"})," religa"]})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"REGEN"})})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsxs)(s.td,{children:["Ranking / p\xe1ginas ",(0,a.jsx)(s.code,{children:"/u/"})]}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"DESLIGADOS \u2014 \xe9 uma flag, volta numa linha"})}),(0,a.jsxs)(s.td,{children:[(0,a.jsx)(s.code,{children:"RANKING_ON"})," em ",(0,a.jsx)(s.code,{children:"src/lib/site.ts"})]})]})]})]}),"\n",(0,a.jsxs)(s.blockquote,{children:["\n",(0,a.jsxs)(s.p,{children:["Bloco gerado por ",(0,a.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,a.jsx)(s.code,{children:"constantes de public/js/game.js \xb7 RANKING_ON de src/lib/site.ts"})]}),"\n"]}),"\n","\n",(0,a.jsxs)(s.p,{children:["O menu aceita de ",(0,a.jsx)(s.strong,{children:"2\xd72 a 8\xd78"})," bots (o motor aceita de 1 a 8 por lado); o padr\xe3o \xe9 4\xd74."]}),"\n",(0,a.jsxs)(s.admonition,{title:"Dois desses s\xe3o escolha recente, n\xe3o defeito",type:"note",children:[(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"A regenera\xe7\xe3o de vida foi desligada"})," em 05/08 (",(0,a.jsx)(s.code,{children:"REGEN = QS.get('regen') === '1'"}),"). Ela\nexistia, estilo CoD \u2014 6 s sem tomar dano e 22 HP/s \u2014, e o dono a reportou como bug\n(",(0,a.jsx)(s.em,{children:'"a vida do 1st player volta a 100, n\xe3o sei porque"'}),") justamente porque era ",(0,a.jsx)(s.strong,{children:"invis\xedvel"}),":\nsem \xedcone, sem som, sem linha nas configura\xe7\xf5es. Regra que o jogador n\xe3o percebe \xe9\nindistingu\xedvel de defeito. Ela continua inteira atr\xe1s de ",(0,a.jsx)(s.code,{children:"?regen=1"}),", com a simetria\njogador\u2194bot. ",(0,a.jsx)(s.strong,{children:"Quem religar tem que entregar o feedback junto"})," \u2014 e resolver o que ela\nvinha tapando: sem cura, kit ou colete, cada vida depois do primeiro contato j\xe1 estava\nperdida."]}),(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"O ranking foi desligado"})," e trocado por telemetria an\xf4nima. ",(0,a.jsx)(s.code,{children:"/ranking"})," e ",(0,a.jsx)(s.code,{children:"/u/*"}),"\nrespondem ",(0,a.jsxs)(s.strong,{children:["200 com aviso + ",(0,a.jsx)(s.code,{children:"noindex"})]})," (n\xe3o 404 \u2014 as URLs est\xe3o indexadas e v\xe3o voltar),\ne ",(0,a.jsx)(s.code,{children:"/api/leaderboard"})," responde ",(0,a.jsx)(s.code,{children:"{disabled:true}"}),"."]})]}),"\n",(0,a.jsxs)(s.admonition,{title:"O quality gate N\xc3O est\xe1 verde, e isso \xe9 declarado",type:"caution",children:[(0,a.jsxs)(s.p,{children:["Quantas invariantes passam ",(0,a.jsx)(s.strong,{children:"n\xe3o \xe9 deriv\xe1vel do c\xf3digo"})," \u2014 \xe9 o resultado de uma execu\xe7\xe3o,\ne depende at\xe9 de qual insumo existe na m\xe1quina. Por isso esse placar n\xe3o \xe9 repetido aqui:\nele mora no cabe\xe7alho de\n",(0,a.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,a.jsx)(s.code,{children:"KNOWN-BUGS.md"})}),", colado\nde uma execu\xe7\xe3o real, com a lista das vermelhas, causa raiz e ",(0,a.jsx)(s.code,{children:"arquivo:linha"})," de cada uma.\n\xc9 esse arquivo que \xe9 mantido dia a dia."]}),(0,a.jsx)(s.p,{children:"Para o estado de hoje, rode \u2014 n\xe3o repita n\xfamero de cabe\xe7a:"}),(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-bash",children:"npm run eval:vm && node tools/eval/invariants.mjs --json # 10-12 min\n"})}),(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"A ordem importa"}),": invariante de viewmodel medida com o JSON de ontem inventa vermelha\n(ver ",(0,a.jsx)(s.a,{href:"/docs/colaborar#rodar-o-quality-gate",children:"Como colaborar"}),")."]})]}),"\n",(0,a.jsx)(s.h2,{id:"rodar-em-3-comandos",children:"Rodar em 3 comandos"}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # abre em http://localhost:4321 \u2014 essa p\xe1gina J\xc1 \xc9 o jogo\n"})}),"\n",(0,a.jsxs)(s.p,{children:["O pacote de \xe1udio (",(0,a.jsx)(s.code,{children:"npm run fetch-audio"}),") \xe9 ",(0,a.jsx)(s.strong,{children:"opcional"}),": sem ele o jogo usa sons\nsintetizados. A pasta ",(0,a.jsx)(s.code,{children:"public/audio/"})," n\xe3o \xe9 versionada."]}),"\n",(0,a.jsx)(s.h3,{id:"linux-webgl-e-modo-compatibilidade",children:"Linux, WebGL e modo compatibilidade"}),"\n",(0,a.jsx)(s.p,{children:"O jogo tenta WebGL2 e WebGL1, come\xe7ando pela escolha padr\xe3o do navegador e reduzindo\nantialias, prefer\xeancia de GPU e stencil antes de desistir. Quando cai em WebGL1,\nllvmpipe/SwiftShader ou outro degrau reduzido, ativa qualidade baixa apenas naquela\nsess\xe3o: DPR 0,75, sem bloom/sombras e com retratos est\xe1ticos na sele\xe7\xe3o."}),"\n",(0,a.jsxs)(s.p,{children:["Use ",(0,a.jsx)(s.code,{children:"?safe=1"})," para priorizar WebGL1 e o caminho de menor custo. Se nem esse modo abrir,\nconfira ",(0,a.jsx)(s.code,{children:"chrome://gpu"})," ou a se\xe7\xe3o Graphics de ",(0,a.jsx)(s.code,{children:"about:support"}),", ligue acelera\xe7\xe3o por\nhardware e atualize Mesa/driver pelo gerenciador da distribui\xe7\xe3o. Uma p\xe1gina n\xe3o pode\nfor\xe7ar um driver quando o navegador recusa criar at\xe9 o contexto WebGL1."]}),"\n",(0,a.jsx)(s.h3,{id:"alternativa-sem-astro-zero-depend\xeancia-de-build",children:"Alternativa sem Astro (zero depend\xeancia de build)"}),"\n",(0,a.jsxs)(s.p,{children:["O arn\xeas de avalia\xe7\xe3o traz um servidor est\xe1tico de 24 linhas que serve ",(0,a.jsx)(s.code,{children:"public/"})," e\nmapeia ",(0,a.jsx)(s.code,{children:"/"})," para o fonte da p\xe1gina do jogo:"]}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-bash",children:"node tools/eval/serve.mjs 8123 # http://localhost:8123\n"})}),"\n",(0,a.jsxs)(s.p,{children:["Ele existe exatamente porque ",(0,a.jsx)(s.code,{children:"src/pages/index.astro"})," \xe9 HTML puro \u2014 d\xe1 pra servir o\narquivo cru sem passar pelo Astro (",(0,a.jsx)(s.code,{children:"tools/eval/serve.mjs:15"}),")."]}),"\n",(0,a.jsx)(s.h2,{id:"a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",children:"A pegadinha que custa a primeira hora de todo mundo"}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsxs)(s.strong,{children:["N\xe3o existe ",(0,a.jsx)(s.code,{children:"public/index.html"}),"."]})," Servir a pasta ",(0,a.jsx)(s.code,{children:"public/"})," estaticamente te d\xe1 um\n\xedndice de diret\xf3rio com ",(0,a.jsx)(s.code,{children:"eval.html"}),", ",(0,a.jsx)(s.code,{children:"mapview.html"})," e companhia \u2014 nenhum deles \xe9 o jogo.\nO HTML do jogo \xe9 ",(0,a.jsx)(s.code,{children:"src/pages/index.astro"}),", servido na ",(0,a.jsx)(s.strong,{children:"rota raiz"})," pelo Astro. N\xe3o h\xe1\nrota ",(0,a.jsx)(s.code,{children:"/game"}),"."]}),"\n",(0,a.jsxs)(s.p,{children:["A confirma\xe7\xe3o independente est\xe1 no pr\xf3prio arn\xeas: ",(0,a.jsx)(s.code,{children:"tools/eval/serve.mjs:15"})," precisa de um\ncaso especial ",(0,a.jsx)(s.code,{children:"if (p === '/')"})," que l\xea ",(0,a.jsx)(s.code,{children:"src/pages/index.astro"})," do disco, justamente porque\nn\xe3o h\xe1 ",(0,a.jsx)(s.code,{children:"index.html"})," em ",(0,a.jsx)(s.code,{children:"public/"})," pra servir."]}),"\n",(0,a.jsx)(s.admonition,{title:"Esta se\xe7\xe3o j\xe1 foi uma lista de erros do README",type:"note",children:(0,a.jsxs)(s.p,{children:["At\xe9 04/08/2026 ela existia porque o ",(0,a.jsx)(s.code,{children:"README.md"})," da raiz mandava rodar\n",(0,a.jsx)(s.code,{children:"cd public && python3 -m http.server"}),' e falava num "jogo em ',(0,a.jsx)(s.code,{children:"/game/"}),'". As duas linhas\nforam corrigidas \u2014 o README hoje diz o certo. O que sobrou \xe9 o fato em si, que continua\nsendo a primeira pedra no caminho de quem chega.']})}),"\n",(0,a.jsx)(s.h2,{id:"estrutura-real-do-reposit\xf3rio",children:"Estrutura real do reposit\xf3rio"}),"\n",(0,a.jsx)(s.p,{children:"Duas zonas de c\xf3digo e uma terceira zona que \xe9 a raz\xe3o desta doc existir (o arn\xeas):"}),"\n",(0,a.jsxs)(s.p,{children:["Nenhuma contagem aqui: a \xe1rvore diz ",(0,a.jsx)(s.strong,{children:"o que \xe9 cada coisa"}),", e os n\xfameros vivem na tabela\ngerada l\xe1 em cima. Misturar os dois \xe9 como o ",(0,a.jsx)(s.code,{children:"ARCH.md"})," escrito \xe0 m\xe3o nasceu errado."]}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{children:'public/ O JOGO \u2014 vanilla ES modules, ZERO build\n js/\n game.js a classe Game (loop, bots, tiro, HUD) \u2014 o maior arquivo do repo\n main.js menu, wiring de DOM, persist\xeancia\n vmattach.js springs.js weapons.js fparms.js handik.js viewmodel/armas\n maps.js o REGISTRO de mapas (quem n\xe3o est\xe1 aqui n\xe3o \xe9 jog\xe1vel)\n map_brasilia.js map_piscina.js map_havan.js\n map_ferrovelho.js map_quebrada.js os mapas registrados\n map_piscinao_ramos.js "Piscin\xe3o" \u2014 existe no disco, FORA do registro\n mapprops.js map_decals.js props e grafite\n bloom.js textures.js vao.js stylize.js gpuparticles.js gr\xe1ficos/FX\n characters.js glbchars.js personagens\n audio.js version.js site-bg.js\n models/ armas, personagens, props e clipes de anima\xe7\xe3o em GLB\n vendor/ Three.js vendorizado (sem CDN, sem npm no runtime)\n style.css o HUD inteiro\n *.html arn\xeases visuais (eval, mapview, weapontest, vm-inspect\u2026)\n\nsrc/ O SITE (Astro + adapter Vercel)\n pages/index.astro \u26a0 ISTO \xc9 O JOGO (HTML + import map + HUD)\n pages/sobre.astro landing/FAQ com JSON-LD\n pages/personagens.astro como-jogar.astro ranking.astro mapa.astro\n pages/u/[...path].astro perfil p\xfablico\n pages/api/*.ts SSR: leaderboard, submit-match, register, badge, avatar\n layouts/Layout.astro shell do site (n\xe3o do jogo)\n lib/ supabase, svg, geo, fmt\n\ntools/\n eval/ O ARN\xcaS \u2014 r\xe9guas, quality gate e sondas. Ver "Quality gates"\n invariants.mjs o quality gate\n ref-measure.py mede os frames de refer\xeancia (a doutrina da casa)\n harness.mjs sobe o Game real em node com DOM stubado\n ARCH.md BAR.md mapa de conflito (gerado) e a r\xe9gua visual\n gen-arch.mjs gera e VALIDA o ARCH.md\n gen-docs.mjs gera e VALIDA os blocos num\xe9ricos desta documenta\xe7\xe3o\n gen-asset.mjs gera prop 3D por texto (Tripo/Meshy)\n gen-image.mjs gera arte 2D por texto (OpenRouter)\n\n (banco: schema/migrations s\xe3o PRIVADOS \u2014 fora do repo)\n.github/workflows/ci.yml o quality gate rodando em CI\n'})}),"\n",(0,a.jsx)(s.p,{children:"Os mapas registrados hoje, e em que modo cada um abre:"}),"\n","\n",(0,a.jsxs)(s.table,{children:[(0,a.jsx)(s.thead,{children:(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.th,{children:"Id"}),(0,a.jsx)(s.th,{children:"Nome no menu"}),(0,a.jsx)(s.th,{children:"Abre em"}),(0,a.jsxs)(s.th,{children:["Arquivo em ",(0,a.jsx)(s.code,{children:"public/js/"})]}),(0,a.jsx)(s.th,{style:{textAlign:"right"},children:"Linhas"})]})}),(0,a.jsxs)(s.tbody,{children:[(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"praca_poderes"})}),(0,a.jsx)(s.td,{children:"Pra\xe7a dos Tr\xeas Poderes"}),(0,a.jsx)(s.td,{children:"rodadas"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_brasilia.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"1.830"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"piscina_treta"})}),(0,a.jsx)(s.td,{children:"Piscina da Treta"}),(0,a.jsx)(s.td,{children:"rodadas"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_piscina.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"810"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"loja_h"})}),(0,a.jsx)(s.td,{children:"Loja H (Estacionamento)"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_havan.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"1.964"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"ferro_velho"})}),(0,a.jsx)(s.td,{children:"Ferro Velho do Z\xe9"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_ferrovelho.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"1.888"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"quebrada"})}),(0,a.jsx)(s.td,{children:"Quebrada (Rua do Baile)"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_quebrada.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"1.599"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"posto_treta"})}),(0,a.jsx)(s.td,{children:"Posto da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_posto.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"489"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"upa_24h"})}),(0,a.jsx)(s.td,{children:"UPA 24h da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_upa.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"288"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"obras_prefeitura"})}),(0,a.jsx)(s.td,{children:"Obras da Prefeitura"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_obras.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"240"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"atacadao_treta"})}),(0,a.jsx)(s.td,{children:"Atacad\xe3o da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_atacadao.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"255"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"parque_treta"})}),(0,a.jsx)(s.td,{children:"Parque da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_parque.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"402"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"velho_oeste"})}),(0,a.jsx)(s.td,{children:"Velho Oeste da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_velho_oeste.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"433"})]}),(0,a.jsxs)(s.tr,{children:[(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"penitenciaria"})}),(0,a.jsx)(s.td,{children:"Penitenci\xe1ria da Treta"}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.strong,{children:"captura"})}),(0,a.jsx)(s.td,{children:(0,a.jsx)(s.code,{children:"map_penitenciaria.js"})}),(0,a.jsx)(s.td,{style:{textAlign:"right"},children:"247"})]})]})]}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.strong,{children:"12 mapas registrados"})," \u2014 2 abrem em rodadas e 10 em captura. ",(0,a.jsx)(s.code,{children:"ctfMode"})," ",(0,a.jsx)(s.strong,{children:"abre"})," o mapa em captura, n\xe3o prende: o jogador troca no menu (\xe9 a ",(0,a.jsx)(s.code,{children:"MOD1"}),"). H\xe1 14 arquivos ",(0,a.jsx)(s.code,{children:"map_*.js"})," em ",(0,a.jsx)(s.code,{children:"public/js/"})," \u2014 arquivo no disco ",(0,a.jsx)(s.strong,{children:"n\xe3o"})," implica mapa jog\xe1vel."]}),"\n",(0,a.jsxs)(s.blockquote,{children:["\n",(0,a.jsxs)(s.p,{children:["Bloco gerado por ",(0,a.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,a.jsx)(s.code,{children:"objeto MAPS de public/js/maps.js"})]}),"\n"]}),"\n","\n",(0,a.jsx)(s.h3,{id:"as-duas-zonas",children:"As duas zonas"}),"\n",(0,a.jsxs)(s.p,{children:["Em uma linha cada: ",(0,a.jsxs)(s.strong,{children:[(0,a.jsx)(s.code,{children:"public/"})," \xe9 o jogo"]})," (vanilla, ES modules, sem framework e sem\nbundler) e ",(0,a.jsxs)(s.strong,{children:[(0,a.jsx)(s.code,{children:"src/"})," \xe9 o site"]})," (Astro com SSR, onde framework \xe9 bem-vindo). O que cada\nregra da fronteira paga, e por que ela \xe9 dura, est\xe1 em\n",(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/stack#as-duas-zonas-e-por-que-a-fronteira-%C3%A9-dura",children:"Stack e ferramentas"})})," \u2014 uma\np\xe1gina s\xf3, para n\xe3o haver duas vers\xf5es da mesma fronteira."]}),"\n",(0,a.jsxs)(s.p,{children:["O que voc\xea precisa saber ",(0,a.jsx)(s.strong,{children:"antes de editar"})," \xe9 a consequ\xeancia: o jogo \xe9 carregado pela\np\xe1gina Astro via ",(0,a.jsx)(s.strong,{children:"import map com vers\xe3o e hash do conte\xfado"})," (",(0,a.jsx)(s.code,{children:"src/pages/index.astro"}),")."]}),"\n",(0,a.jsx)(s.admonition,{title:"Preserve o manifesto publicado",type:"danger",children:(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.code,{children:"scripts/module-cache.mjs"})," deriva o hash dos m\xf3dulos publicados sob ",(0,a.jsx)(s.code,{children:"public/js/"})," e o import map\naplica essa revis\xe3o ao grafo inteiro. N\xe3o fa\xe7a bump manual e n\xe3o inclua bancadas que\n",(0,a.jsx)(s.code,{children:"scripts/prune-dist.mjs"})," remove. ",(0,a.jsx)(s.code,{children:"npm run eval:shaderbudget"})," (SB7) confere as duas propriedades."]})}),"\n",(0,a.jsx)(s.h2,{id:"comandos-que-voc\xea-vai-usar",children:"Comandos que voc\xea vai usar"}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-bash",children:"npm run dev # site + jogo (Astro, :4321) \u2014 a rota / J\xc1 \xc9 o jogo\nnpm run build # dist/client + dist/server\nnpm run eval:vm # enquadramento do viewmodel \u2014 RODE ANTES das invariantes\nnpm run eval:invariants # as invariantes \u2014 node puro, 10-12 min\nnpm run eval:bots # botsim 60 s por mapa, sementes fixas\nnpm run eval:mat # material/luz/fog/textura nos mapas\nnpm run docs # regenera os blocos num\xe9ricos desta documenta\xe7\xe3o\nnode tools/eval/serve.mjs 8123 # servidor est\xe1tico sem Astro\n"})}),"\n",(0,a.jsxs)(s.p,{children:["E os dois quality gates, com a lista exata do que cada um roda \u2014 direto do ",(0,a.jsx)(s.code,{children:"package.json"}),":"]}),"\n","\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-bash",children:"npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:preload eval:docsautoria\n"})}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.code,{children:"package.json"})," tem ",(0,a.jsx)(s.strong,{children:"117 scripts"}),"; o motivo de cada um mora em ",(0,a.jsx)(s.code,{children:"SCRIPTS.md"})," (migrado das chaves ",(0,a.jsx)(s.code,{children:"//nome"})," em 18/08/2026) \u2014 \xe9 onde est\xe1 o porqu\xea."]}),"\n",(0,a.jsxs)(s.blockquote,{children:["\n",(0,a.jsxs)(s.p,{children:["Bloco gerado por ",(0,a.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,a.jsx)(s.code,{children:"node -p \"Object.keys(require('./package.json').scripts)\""})]}),"\n"]}),"\n","\n",(0,a.jsxs)(s.p,{children:["O ",(0,a.jsx)(s.code,{children:"check:fast"})," cobre as r\xe9guas de node puro; o CI (",(0,a.jsx)(s.code,{children:".github/workflows/ci.yml"}),") roda o\nmesmo conjunto mais os passos que exigem rede."]}),"\n",(0,a.jsxs)(s.admonition,{type:"tip",children:[(0,a.jsxs)(s.mdxAdmonitionTitle,{children:["Use o ",(0,a.jsx)(s.code,{children:"check:fast"})," no loop, o ",(0,a.jsx)(s.code,{children:"portao-browser"})," antes do PR"]}),(0,a.jsxs)(s.p,{children:["O ",(0,a.jsx)(s.code,{children:"portao-browser"})," gasta 10-12 min porque sobe o jogo cinco vezes. O ",(0,a.jsx)(s.code,{children:"check:fast"})," cobre as r\xe9guas\nque nasceram dos bugs mais recentes (menu de pausa, rodada de captura, regenera\xe7\xe3o,\nmanifesto de anima\xe7\xe3o) e roda em cerca de um minuto."]})]}),"\n",(0,a.jsx)(s.h2,{id:"onde-ir-agora",children:"Onde ir agora"}),"\n",(0,a.jsxs)(s.p,{children:["A ordem da barra lateral ",(0,a.jsx)(s.strong,{children:"\xe9"})," a ordem de leitura, e cada p\xe1gina entrega uma coisa:"]}),"\n",(0,a.jsxs)(s.ol,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/stack",children:"Stack e ferramentas"})})," \u2014 com o que isso \xe9 feito, com a vers\xe3o declarada\nde cada pe\xe7a. \xc9 onde a fronteira ",(0,a.jsx)(s.code,{children:"public/"})," \xd7 ",(0,a.jsx)(s.code,{children:"src/"})," est\xe1 explicada por inteiro."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/instrumentacao-ai",children:"Instrumenta\xe7\xe3o de IA"})})," \u2014 como o trabalho \xe9 feito aqui. Se\nvoc\xea nunca colaborou com agentes num reposit\xf3rio, comece por essa."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/quality-gates",children:"O quality gate"})})," \u2014 o que \xe9 uma invariante, como se escreve uma, as\nduas leis da casa e o teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua. ",(0,a.jsx)(s.strong,{children:"\xc9 a p\xe1gina mais \xfatil do\nsite."})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/arquitetura",children:"Arquitetura"})})," \u2014 como N agentes editam o mesmo arquivo sem\ncolidir, e a tabela de conflito. Leia antes de tocar em ",(0,a.jsx)(s.code,{children:"game.js"}),"."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/colaborar",children:"Como colaborar"})})," \u2014 o que um PR precisa pra entrar, e as ",(0,a.jsx)(s.strong,{children:"tarefas\nde primeira contribui\xe7\xe3o"})," j\xe1 escritas em\n",(0,a.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/tree/main/docs/issues",children:(0,a.jsx)(s.code,{children:"docs/issues/"})})," (com um\n",(0,a.jsx)(s.code,{children:"abrir-issues.sh"})," pronto \u2014 elas ainda n\xe3o foram abertas no GitHub)."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"Licen\xe7a"})," \u2014 o ",(0,a.jsx)(s.code,{children:"LICENSE"})," na raiz declara (hoje AGPL-3.0); as superf\xedcies que\nrepetem o nome e mudam junto est\xe3o no ",(0,a.jsx)(s.code,{children:"CONTRIBUTING.md"}),"."]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:(0,a.jsx)(s.a,{href:"/docs/estado",children:"Estado atual"})})," \u2014 fontes vivas de produ\xe7\xe3o, dados e d\xedvida conhecida\ndesde a \xfaltima medi\xe7\xe3o colada."]}),"\n"]}),"\n",(0,a.jsxs)(s.p,{children:["Para onde o projeto ",(0,a.jsx)(s.strong,{children:"vai"})," n\xe3o est\xe1 nesta documenta\xe7\xe3o: \xe9 o\n",(0,a.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,a.jsx)(s.code,{children:"docs/ROADMAP.md"})}),", e o\nplano execut\xe1vel \xe9 o\n",(0,a.jsx)(s.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/plans/08-RELEASE-PROFISSIONAL.md",children:(0,a.jsx)(s.code,{children:"plans/08"})}),"."]})]})}function j(e={}){const{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,a.jsx)(s,{...e,children:(0,a.jsx)(h,{...e})}):h(e)}},8453(e,s,r){r.d(s,{R:()=>o,x:()=>i});var d=r(6540);const a={},n=d.createContext(a);function o(e){const s=d.useContext(n);return d.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function i(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:o(e.components),d.createElement(n.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/assets/js/393ca303.bca993db.js b/public/docs/assets/js/393ca303.501859ff.js
similarity index 99%
rename from public/docs/assets/js/393ca303.bca993db.js
rename to public/docs/assets/js/393ca303.501859ff.js
index e839fd33a..4a23374f9 100644
--- a/public/docs/assets/js/393ca303.bca993db.js
+++ b/public/docs/assets/js/393ca303.501859ff.js
@@ -1 +1 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[514],{1757(e,o,a){a.r(o),a.d(o,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>d,metadata:()=>s,toc:()=>l});const s=JSON.parse('{"id":"colaborar","title":"Como colaborar","description":"Setup, como rodar o quality gate, o que um PR precisa, como adicionar arma / personagem / mapa, e as boas primeiras tarefas.","source":"@site/docs/colaborar.md","sourceDirName":".","slug":"/colaborar","permalink":"/docs/colaborar","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/colaborar.md","tags":[],"version":"current","sidebarPosition":6,"frontMatter":{"id":"colaborar","title":"Como colaborar","sidebar_label":"Como colaborar","sidebar_position":6,"description":"Setup, como rodar o quality gate, o que um PR precisa, como adicionar arma / personagem / mapa, e as boas primeiras tarefas."},"sidebar":"dev","previous":{"title":"Arquitetura","permalink":"/docs/arquitetura"},"next":{"title":"Estado atual","permalink":"/docs/estado"}}');var r=a(4848),n=a(8453);const d={id:"colaborar",title:"Como colaborar",sidebar_label:"Como colaborar",sidebar_position:6,description:"Setup, como rodar o quality gate, o que um PR precisa, como adicionar arma / personagem / mapa, e as boas primeiras tarefas."},i="Como colaborar",c={},l=[{value:"Setup",id:"setup",level:2},{value:"Rodar o quality gate",id:"rodar-o-quality-gate",level:2},{value:"Antes de dizer que consertou: mute",id:"antes-de-dizer-que-consertou-mute",level:3},{value:"Se o seu PR \xe9 um conserto de bug",id:"se-o-seu-pr-\xe9-um-conserto-de-bug",level:3},{value:"O que um PR precisa",id:"o-que-um-pr-precisa",level:2},{value:"1. Uma invariante nova \u2014 ou a raz\xe3o de n\xe3o precisar",id:"1-uma-invariante-nova--ou-a-raz\xe3o-de-n\xe3o-precisar",level:3},{value:"2. O quality gate n\xe3o pode piorar",id:"2-o-quality-gate-n\xe3o-pode-piorar",level:3},{value:"3. N\xfameros, com node tools/gen-docs.mjs. Fonte: git ls-files public/js/*.js | xargs wc -larquivo:linha",id:"3-n\xfameros-com-arquivolinha",level:3},{value:"4. Uma frente por PR",id:"4-uma-frente-por-pr",level:3},{value:"5. Higiene do reposit\xf3rio",id:"5-higiene-do-reposit\xf3rio",level:3},{value:"6. Linha editorial",id:"6-linha-editorial",level:3},{value:"Como adicionar uma arma",id:"como-adicionar-uma-arma",level:2},{value:"Como adicionar um personagem",id:"como-adicionar-um-personagem",level:2},{value:"Como adicionar um mapa",id:"como-adicionar-um-mapa",level:2},{value:"Boas primeiras tarefas",id:"boas-primeiras-tarefas",level:2},{value:"Muito boas para o primeiro PR",id:"muito-boas-para-o-primeiro-pr",level:3},{value:"Trabalho de verdade, ainda acess\xedvel",id:"trabalho-de-verdade-ainda-acess\xedvel",level:3},{value:"Alto valor, precisa de conversa antes",id:"alto-valor-precisa-de-conversa-antes",level:3},{value:"Processo",id:"processo",level:2}];function t(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,n.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(o.header,{children:(0,r.jsx)(o.h1,{id:"como-colaborar",children:"Como colaborar"})}),"\n",(0,r.jsxs)(o.p,{children:["O n\xfamero abaixo n\xe3o \xe9 ret\xf3rica, e n\xe3o \xe9 escrito \xe0 m\xe3o: sai de ",(0,r.jsx)(o.code,{children:"git shortlog -sn --no-merges"})," descontando os autores que s\xe3o agentes de IA (que assinam como ",(0,r.jsx)(o.code,{children:"Claude"})," /\n",(0,r.jsx)(o.code,{children:"Claude (gauntlet \u2026)"}),")."]}),"\n","\n",(0,r.jsxs)(o.p,{children:[(0,r.jsx)(o.strong,{children:"11 identidades de autoria humana"})," assinam commit no hist\xf3rico ",(0,r.jsx)(o.strong,{children:"desta branch"}),": ",(0,r.jsx)(o.code,{children:"ruben-cytonic"}),", ",(0,r.jsx)(o.code,{children:"Emerson Garrido"}),", ",(0,r.jsx)(o.code,{children:"Ruben"}),", ",(0,r.jsx)(o.code,{children:"rubenmarcus"}),", ",(0,r.jsx)(o.code,{children:"Ruben Marcus"}),", ",(0,r.jsx)(o.code,{children:"William Oliveira"}),", ",(0,r.jsx)(o.code,{children:"Juan Versolato Lopes"}),", ",(0,r.jsx)(o.code,{children:"daeeseD"}),", ",(0,r.jsx)(o.code,{children:"matheusgb"}),", ",(0,r.jsx)(o.code,{children:"Man\xe1 Soares"}),", ",(0,r.jsx)(o.code,{children:"daltonfontes"}),". O resto dos commits \xe9 assinado por agentes de IA. Branch n\xe3o \xe9 reposit\xf3rio: quem contribuiu num ramo que esta branch n\xe3o cont\xe9m ",(0,r.jsx)(o.strong,{children:"n\xe3o aparece aqui"}),"."]}),"\n",(0,r.jsxs)(o.blockquote,{children:["\n",(0,r.jsxs)(o.p,{children:["Bloco gerado por ",(0,r.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,r.jsx)(o.code,{children:"git shortlog -sn --no-merges (descontando autores que s\xe3o agentes)"})]}),"\n"]}),"\n","\n",(0,r.jsx)(o.p,{children:"N\xe3o existe time, n\xe3o existe comunidade, n\xe3o existe fila de revisores \u2014 existem essas\npessoas e um quality gate automatizado."}),"\n",(0,r.jsx)(o.admonition,{title:"O bloco acima conta a BRANCH, e o projeto \xe9 maior que ela",type:"note",children:(0,r.jsxs)(o.p,{children:["A ",(0,r.jsx)(o.code,{children:"main"})," tem um quarto contribuidor que esta branch de trabalho n\xe3o cont\xe9m \u2014 13 commits\nde um cliente desktop, mesclados em julho. Quem, quanto e por que isso importa para\nqualquer decis\xe3o de licen\xe7a est\xe1 no ",(0,r.jsx)(o.code,{children:"CONTRIBUTING.md"})," (se\xe7\xe3o de licen\xe7a e superf\xedcies)."]})}),"\n",(0,r.jsxs)(o.p,{children:["Isso \xe9 relevante pra voc\xea de duas formas opostas. A ruim: se o seu PR travar, pode\ndemorar. A boa: ",(0,r.jsx)(o.strong,{children:"quase toda a r\xe9gua \xe9 m\xe1quina."})," ",(0,r.jsx)(o.code,{children:"npm run check:fast"})," te d\xe1 o mesmo veredito\nque o mantenedor daria, antes de voc\xea abrir o PR, sem esperar ningu\xe9m. A barreira \xe9 baixa\n",(0,r.jsx)(o.strong,{children:"de prop\xf3sito"})," \u2014 \xe9 um dos princ\xedpios que n\xe3o mudam do\n",(0,r.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,r.jsx)(o.code,{children:"docs/ROADMAP.md"})}),".\nMas a r\xe9gua n\xe3o \xe9."]}),"\n",(0,r.jsxs)(o.p,{children:["Resumo em uma frase: ",(0,r.jsx)(o.strong,{children:"traga o n\xfamero."})," Um PR que muda comportamento vis\xedvel e n\xe3o traz\nnem uma invariante nova nem a raz\xe3o de n\xe3o precisar de uma vai voltar com uma pergunta."]}),"\n",(0,r.jsx)(o.h2,{id:"setup",children:"Setup"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # http://localhost:4321 \u2014 a rota raiz J\xc1 \xc9 o jogo\n"})}),"\n",(0,r.jsx)(o.p,{children:"Opcional:"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"npm run fetch-audio # pacote de \xe1udio (sem ele: sons sintetizados)\n"})}),"\n",(0,r.jsxs)(o.p,{children:["Requisitos: Node 22 (\xe9 o que o CI usa, ",(0,r.jsx)(o.code,{children:".github/workflows/ci.yml:14"}),") e Python 3 para\nparte do arn\xeas (",(0,r.jsx)(o.code,{children:"ref-measure.py"}),", ",(0,r.jsx)(o.code,{children:"char_probe.py"}),", ",(0,r.jsx)(o.code,{children:"mat_shade.py"})," \u2014 usam numpy e PIL)."]}),"\n",(0,r.jsxs)(o.admonition,{type:"caution",children:[(0,r.jsxs)(o.mdxAdmonitionTitle,{children:["Servir ",(0,r.jsx)(o.code,{children:"public/"})," N\xc3O roda o jogo"]}),(0,r.jsxs)(o.p,{children:["N\xe3o existe ",(0,r.jsx)(o.code,{children:"public/index.html"}),": o HTML do jogo \xe9 ",(0,r.jsx)(o.code,{children:"src/pages/index.astro"}),", na rota raiz.\nUse ",(0,r.jsx)(o.code,{children:"npm run dev"}),". Detalhes e prova em\n",(0,r.jsx)(o.a,{href:"/docs/#a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",children:"Come\xe7ando"}),"."]})]}),"\n",(0,r.jsx)(o.h2,{id:"rodar-o-quality-gate",children:"Rodar o quality gate"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"npm run eval:vm # OBRIGAT\xd3RIO ANTES \u2014 ver o aviso abaixo\nnode tools/eval/invariants.mjs # o quality gate inteiro\nnode tools/eval/invariants.mjs --json # sa\xedda pra m\xe1quina\nnpm run check:fast # syntax + quality gate (r\xe9guas de node puro)\n"})}),"\n",(0,r.jsxs)(o.admonition,{type:"danger",children:[(0,r.jsxs)(o.mdxAdmonitionTitle,{children:[(0,r.jsx)(o.code,{children:"eval:vm"})," roda ANTES de ",(0,r.jsx)(o.code,{children:"invariants.mjs"}),". Sempre."]}),(0,r.jsxs)(o.p,{children:["As invariantes de viewmodel (VM1\u2013VM19) ",(0,r.jsx)(o.strong,{children:"leem"})," ",(0,r.jsx)(o.code,{children:"tools/eval/vm_mint_audit.json"}),", que \xe9 o\n",(0,r.jsx)(o.code,{children:"eval:vm"})," quem ",(0,r.jsx)(o.strong,{children:"escreve"}),". Rodar as invariantes com esse JSON velho mede o viewmodel de\nontem e ",(0,r.jsx)(o.strong,{children:"inventa vermelha"}),": em 04/08/2026 o JSON estava em ",(0,r.jsx)(o.code,{children:"V0=80\xb0"})," contra o ",(0,r.jsx)(o.code,{children:"game.js"}),"\nem ",(0,r.jsx)(o.code,{children:"V0=42\xb0"}),", e a VM5 acusava ",(0,r.jsx)(o.strong,{children:"26/26 armas fora"}),"; depois de ",(0,r.jsx)(o.code,{children:"npm run eval:vm"}),", ",(0,r.jsx)(o.strong,{children:"3/26"}),".\nA VM1 caiu de 26/26 para 2/26 e a VM9 ficou verde."]}),(0,r.jsxs)(o.p,{children:["A ordem do ",(0,r.jsx)(o.code,{children:"npm run check"})," j\xe1 foi corrigida (",(0,r.jsx)(o.code,{children:"package.json"}),") \u2014 o cuidado \xe9 para quando\nvoc\xea chamar ",(0,r.jsx)(o.code,{children:"node tools/eval/invariants.mjs"})," na m\xe3o. Detalhe: BUG-02 do\n",(0,r.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"})}),"."]})]}),"\n",(0,r.jsxs)(o.p,{children:["Custo real: numa m\xe1quina de 2 CPUs, ",(0,r.jsx)(o.strong,{children:"cerca de 10 minutos"}),". Ele sobe o jogo real cinco\nvezes (uma por mapa), roda 60 s de simula\xe7\xe3o de bot por mapa e audita todos os GLBs de arma.\nRode antes de abrir o PR, n\xe3o depois de receber a review."]}),"\n",(0,r.jsx)(o.p,{children:"Arn\xeases individuais, quando voc\xea quiser iterar r\xe1pido numa frente s\xf3:"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"node tools/eval/vm-mint-audit.mjs # enquadramento de viewmodel (todo o arsenal)\nnode tools/eval/vm-solve.mjs # existe ponto vi\xe1vel pras invariantes de VM?\nnode tools/eval/vm-solve.mjs --atual # s\xf3 as margens da config atual (instant\xe2neo)\nnode tools/eval/botsim.mjs 60 all # navega\xe7\xe3o de bots, todos os mapas, sementes fixas\nnode tools/eval/char-probe.mjs # personagens (C1..C6)\nnode tools/eval/map-check.mjs all # geometria de mapa (MAP1-MAP3, CTF1)\nnode tools/eval/mat-check.mjs # material/luz/fog/textura\nnode tools/eval/pickup-check.mjs # todo pickup \xe9 alcan\xe7\xe1vel?\nnode tools/eval/ui-check.mjs # UI1 contraste \xb7 UI2 polui\xe7\xe3o \xb7 UI3 \xe1rea morta \xb7 UI4 ritmo\n"})}),"\n",(0,r.jsx)(o.h3,{id:"antes-de-dizer-que-consertou-mute",children:"Antes de dizer que consertou: mute"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # espera UI1 VERMELHA\n"})}),"\n",(0,r.jsxs)(o.p,{children:["Desfa\xe7a a sua pr\xf3pria corre\xe7\xe3o e confira que o quality gate ",(0,r.jsx)(o.strong,{children:"fica vermelho"}),". Se ficar verde,\no que voc\xea mediu n\xe3o \xe9 o que voc\xea consertou. \xc9 a li\xe7\xe3o mais cara deste reposit\xf3rio e ela\ntem uma p\xe1gina inteira: ",(0,r.jsx)(o.a,{href:"/docs/quality-gates#teste-de-muta%C3%A7%C3%A3o-da-pr%C3%B3pria-r%C3%A9gua",children:"Teste de muta\xe7\xe3o"}),"."]}),"\n",(0,r.jsx)(o.h3,{id:"se-o-seu-pr-\xe9-um-conserto-de-bug",children:"Se o seu PR \xe9 um conserto de bug"}),"\n",(0,r.jsxs)(o.p,{children:["Use a skill ",(0,r.jsx)(o.code,{children:"bug-hunt"})," (",(0,r.jsx)(o.code,{children:".claude/skills/bug-hunt/SKILL.md"}),"). Ela \xe9 o passo a passo desta\ndoutrina aplicado a defeito \u2014 com o caso real que comprou cada regra, o gabarito da entrada\ndo ",(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"})," e o do relat\xf3rio final, incluindo como declarar o que voc\xea ",(0,r.jsx)(o.strong,{children:"n\xe3o"}),"\nverificou. Serve para agente e para gente."]}),"\n",(0,r.jsx)(o.h2,{id:"o-que-um-pr-precisa",children:"O que um PR precisa"}),"\n",(0,r.jsx)(o.h3,{id:"1-uma-invariante-nova--ou-a-raz\xe3o-de-n\xe3o-precisar",children:"1. Uma invariante nova \u2014 ou a raz\xe3o de n\xe3o precisar"}),"\n",(0,r.jsxs)(o.p,{children:["Esta \xe9 a regra que define o projeto. Todo PR que muda ",(0,r.jsx)(o.strong,{children:"comportamento observ\xe1vel"})," traz\numa das duas coisas:"]}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Uma invariante nova"})," em ",(0,r.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),", com teto que tem proced\xeancia\n(arquivo de refer\xeancia + pixel medido + script que reproduz), ou"]}),"\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Uma frase na descri\xe7\xe3o do PR"}),' dizendo por que n\xe3o precisa. Raz\xf5es v\xe1lidas: "j\xe1 \xe9\ncoberto pela invariante X" (diga qual), "\xe9 refatora\xe7\xe3o sem mudan\xe7a observ\xe1vel \u2014 o\nquality gate d\xe1 o mesmo placar antes e depois" (cole os dois), "\xe9 conte\xfado puro (texto,\nasset) sem regra de jogo associada".']}),"\n"]}),"\n",(0,r.jsx)(o.p,{children:'Raz\xe3o inv\xe1lida: "testei manualmente e ficou bom".'}),"\n",(0,r.jsxs)(o.p,{children:["Por qu\xea: ",(0,r.jsx)(o.strong,{children:"inten\xe7\xe3o que n\xe3o vira invariante \xe9 otimizada para fora"}),". Uma rodada levou o\nquality gate de 16/21 para 19/21 sem afrouxar um teto sequer, e foi reprovada, porque destruiu\nem sil\xeancio uma decis\xe3o est\xe9tica que nenhuma invariante codificava. Caso completo em\n",(0,r.jsx)(o.a,{href:"/docs/quality-gates#lei-1--inten%C3%A7%C3%A3o-que-n%C3%A3o-vira-invariante-%C3%A9-otimizada-para-fora",children:"O quality gate"}),"."]}),"\n",(0,r.jsx)(o.h3,{id:"2-o-quality-gate-n\xe3o-pode-piorar",children:"2. O quality gate n\xe3o pode piorar"}),"\n",(0,r.jsxs)(o.p,{children:["Cole a sa\xedda de ",(0,r.jsx)(o.code,{children:"node tools/eval/invariants.mjs"})," antes e depois. Se alguma cr\xedtica ficou\nvermelha, o PR n\xe3o entra. Se voc\xea ",(0,r.jsx)(o.strong,{children:"consertou"})," uma vermelha, diga qual e mostre."]}),"\n",(0,r.jsxs)(o.p,{children:["O estado do quality gate deve ser medido (",(0,r.jsx)(o.a,{href:"/docs/estado",children:"veja como"}),"); a lista viva com causa raiz est\xe1\nno ",(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"}),"). Isso n\xe3o \xe9 licen\xe7a para piorar: o compromisso \xe9 ",(0,r.jsx)(o.em,{children:'"a sua mudan\xe7a n\xe3o\nacrescenta vermelho"'}),"."]}),"\n",(0,r.jsxs)(o.h3,{id:"3-n\xfameros-com-arquivolinha",children:["3. N\xfameros, com ",(0,r.jsx)(o.code,{children:"arquivo:linha"})]}),"\n",(0,r.jsxs)(o.p,{children:['A afirma\xe7\xe3o "melhorei a ilumina\xe7\xe3o" n\xe3o \xe9 revis\xe1vel. "O ch\xe3o do ',(0,r.jsx)(o.code,{children:"praca_poderes"})," estava 8 pontos\nde L* acima das paredes, causa em ",(0,r.jsx)(o.code,{children:"map_brasilia.js:NNN"}),', corrigido para X" \xe9. Essa\nexig\xeancia n\xe3o \xe9 estilo \u2014 \xe9 o que permite que a pr\xf3xima rodada confira o seu trabalho.']}),"\n",(0,r.jsx)(o.h3,{id:"4-uma-frente-por-pr",children:"4. Uma frente por PR"}),"\n",(0,r.jsxs)(o.p,{children:["Consulte a tabela de conflito (",(0,r.jsx)(o.a,{href:"/docs/arquitetura#a-tabela-de-conflito",children:"Arquitetura"}),"). Um\nPR que toca armas + UI + mapa \xe9 tr\xeas PRs escondidos, e vai colidir com tr\xeas frentes. Em\n",(0,r.jsx)(o.code,{children:"game.js"}),", edite por trecho; nunca sobrescreva o arquivo inteiro."]}),"\n",(0,r.jsx)(o.h3,{id:"5-higiene-do-reposit\xf3rio",children:"5. Higiene do reposit\xf3rio"}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.code,{children:"node --check"})," em cada arquivo de ",(0,r.jsx)(o.code,{children:"public/js/"})," que voc\xea editou (o CI faz isso primeiro,\n",(0,r.jsx)(o.code,{children:".github/workflows/ci.yml:19-20"}),")."]}),"\n",(0,r.jsxs)(o.li,{children:["Coment\xe1rio ",(0,r.jsx)(o.strong,{children:"em portugu\xeas explicando o porqu\xea"}),", n\xe3o o qu\xea. \xc9 a cultura do repo e \xe9 o\nque sobrevive ao pr\xf3ximo handoff."]}),"\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Nunca delete coment\xe1rio de proced\xeancia"})," num PR de limpeza. Aquele par\xe1grafo longo\nexplicando de onde veio o n\xfamero 0,513 \xe9 o que impede a pr\xf3xima rodada de repetir tr\xeas\ndias perdidos."]}),"\n",(0,r.jsxs)(o.li,{children:["Mexeu em ",(0,r.jsx)(o.code,{children:"public/js/*.js"}),"? ",(0,r.jsxs)(o.strong,{children:["Bump o ",(0,r.jsx)(o.code,{children:"?v="})," nos dois lados"]})," \u2014 ",(0,r.jsx)(o.code,{children:"public/js/version.js"})," e o\nimport map de ",(0,r.jsx)(o.code,{children:"src/pages/index.astro"}),'. J\xe1 custou dias de "corre\xe7\xe3o que n\xe3o chegava".']}),"\n",(0,r.jsxs)(o.li,{children:["Mexeu em ",(0,r.jsx)(o.code,{children:"public/js/*.js"}),", no ",(0,r.jsx)(o.code,{children:"maps.js"}),", no ",(0,r.jsx)(o.code,{children:"characters.js"})," ou numa depend\xeancia?\n",(0,r.jsxs)(o.strong,{children:["Rode ",(0,r.jsx)(o.code,{children:"npm run docs"})]})," e commite junto. O ",(0,r.jsx)(o.code,{children:"docs:check"})," est\xe1 no ",(0,r.jsx)(o.code,{children:"check:fast"})," e vai\nreprovar \u2014 leva menos de um segundo e \xe9 o que impede a doc de voltar a mentir."]}),"\n",(0,r.jsxs)(o.li,{children:["Nada de asset com copyright. Nada de ",(0,r.jsx)(o.code,{children:"service_role"})," key commitada."]}),"\n",(0,r.jsx)(o.li,{children:"Nada de depend\xeancia de runtime no jogo. Three.js \xe9 vendorizado; o jogo tem que rodar\narrastando a pasta pra um host est\xe1tico."}),"\n"]}),"\n",(0,r.jsx)(o.h3,{id:"6-linha-editorial",children:"6. Linha editorial"}),"\n",(0,r.jsxs)(o.p,{children:["De ",(0,r.jsx)(o.code,{children:"CONTRIBUTING.md:7-16"}),": o jogo n\xe3o tem lado pol\xedtico (os dois times t\xeam a mesma\nmec\xe2nica), n\xe3o incita \xf3dio, n\xe3o usa pessoas reais \u2014 s\xf3 arqu\xe9tipos originais, sem gore.\nContribui\xe7\xf5es que violem isso s\xe3o recusadas. N\xe3o \xe9 burocracia: \xe9 o que protege o projeto\nde takedown e de virar outra coisa."]}),"\n",(0,r.jsx)(o.h2,{id:"como-adicionar-uma-arma",children:"Como adicionar uma arma"}),"\n",(0,r.jsxs)(o.p,{children:["O pipeline \xe9 data-driven a partir do GLB. Os GLBs de arma vivem em ",(0,r.jsx)(o.code,{children:"public/models/weapons/"}),"\n(a contagem est\xe1 no bloco gerado de ",(0,r.jsx)(o.a,{href:"/docs/",children:"Come\xe7ando"}),")."]}),"\n",(0,r.jsxs)(o.ol,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Coloque o GLB"})," em ",(0,r.jsx)(o.code,{children:"public/models/weapons/arquivo:linha",id:"3-n\xfameros-com-arquivolinha",level:3},{value:"4. Uma frente por PR",id:"4-uma-frente-por-pr",level:3},{value:"5. Higiene do reposit\xf3rio",id:"5-higiene-do-reposit\xf3rio",level:3},{value:"6. Linha editorial",id:"6-linha-editorial",level:3},{value:"Como adicionar uma arma",id:"como-adicionar-uma-arma",level:2},{value:"Como adicionar um personagem",id:"como-adicionar-um-personagem",level:2},{value:"Como adicionar um mapa",id:"como-adicionar-um-mapa",level:2},{value:"Boas primeiras tarefas",id:"boas-primeiras-tarefas",level:2},{value:"Muito boas para o primeiro PR",id:"muito-boas-para-o-primeiro-pr",level:3},{value:"Trabalho de verdade, ainda acess\xedvel",id:"trabalho-de-verdade-ainda-acess\xedvel",level:3},{value:"Alto valor, precisa de conversa antes",id:"alto-valor-precisa-de-conversa-antes",level:3},{value:"Processo",id:"processo",level:2}];function t(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,n.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(o.header,{children:(0,r.jsx)(o.h1,{id:"como-colaborar",children:"Como colaborar"})}),"\n",(0,r.jsxs)(o.p,{children:["O n\xfamero abaixo n\xe3o \xe9 ret\xf3rica, e n\xe3o \xe9 escrito \xe0 m\xe3o: sai de ",(0,r.jsx)(o.code,{children:"git shortlog -sn --no-merges"})," descontando os autores que s\xe3o agentes de IA (que assinam como ",(0,r.jsx)(o.code,{children:"Claude"})," /\n",(0,r.jsx)(o.code,{children:"Claude (gauntlet \u2026)"}),")."]}),"\n","\n",(0,r.jsxs)(o.p,{children:[(0,r.jsx)(o.strong,{children:"11 identidades de autoria humana"})," assinam commit no hist\xf3rico ",(0,r.jsx)(o.strong,{children:"desta branch"}),": ",(0,r.jsx)(o.code,{children:"ruben-cytonic"}),", ",(0,r.jsx)(o.code,{children:"Ruben"}),", ",(0,r.jsx)(o.code,{children:"Emerson Garrido"}),", ",(0,r.jsx)(o.code,{children:"rubenmarcus"}),", ",(0,r.jsx)(o.code,{children:"Ruben Marcus"}),", ",(0,r.jsx)(o.code,{children:"William Oliveira"}),", ",(0,r.jsx)(o.code,{children:"Juan Versolato Lopes"}),", ",(0,r.jsx)(o.code,{children:"daeeseD"}),", ",(0,r.jsx)(o.code,{children:"matheusgb"}),", ",(0,r.jsx)(o.code,{children:"Man\xe1 Soares"}),", ",(0,r.jsx)(o.code,{children:"daltonfontes"}),". O resto dos commits \xe9 assinado por agentes de IA. Branch n\xe3o \xe9 reposit\xf3rio: quem contribuiu num ramo que esta branch n\xe3o cont\xe9m ",(0,r.jsx)(o.strong,{children:"n\xe3o aparece aqui"}),"."]}),"\n",(0,r.jsxs)(o.blockquote,{children:["\n",(0,r.jsxs)(o.p,{children:["Bloco gerado por ",(0,r.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,r.jsx)(o.code,{children:"git shortlog -sn --no-merges (descontando autores que s\xe3o agentes)"})]}),"\n"]}),"\n","\n",(0,r.jsx)(o.p,{children:"N\xe3o existe time, n\xe3o existe comunidade, n\xe3o existe fila de revisores \u2014 existem essas\npessoas e um quality gate automatizado."}),"\n",(0,r.jsx)(o.admonition,{title:"O bloco acima conta a BRANCH, e o projeto \xe9 maior que ela",type:"note",children:(0,r.jsxs)(o.p,{children:["A ",(0,r.jsx)(o.code,{children:"main"})," tem um quarto contribuidor que esta branch de trabalho n\xe3o cont\xe9m \u2014 13 commits\nde um cliente desktop, mesclados em julho. Quem, quanto e por que isso importa para\nqualquer decis\xe3o de licen\xe7a est\xe1 no ",(0,r.jsx)(o.code,{children:"CONTRIBUTING.md"})," (se\xe7\xe3o de licen\xe7a e superf\xedcies)."]})}),"\n",(0,r.jsxs)(o.p,{children:["Isso \xe9 relevante pra voc\xea de duas formas opostas. A ruim: se o seu PR travar, pode\ndemorar. A boa: ",(0,r.jsx)(o.strong,{children:"quase toda a r\xe9gua \xe9 m\xe1quina."})," ",(0,r.jsx)(o.code,{children:"npm run check:fast"})," te d\xe1 o mesmo veredito\nque o mantenedor daria, antes de voc\xea abrir o PR, sem esperar ningu\xe9m. A barreira \xe9 baixa\n",(0,r.jsx)(o.strong,{children:"de prop\xf3sito"})," \u2014 \xe9 um dos princ\xedpios que n\xe3o mudam do\n",(0,r.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,r.jsx)(o.code,{children:"docs/ROADMAP.md"})}),".\nMas a r\xe9gua n\xe3o \xe9."]}),"\n",(0,r.jsxs)(o.p,{children:["Resumo em uma frase: ",(0,r.jsx)(o.strong,{children:"traga o n\xfamero."})," Um PR que muda comportamento vis\xedvel e n\xe3o traz\nnem uma invariante nova nem a raz\xe3o de n\xe3o precisar de uma vai voltar com uma pergunta."]}),"\n",(0,r.jsx)(o.h2,{id:"setup",children:"Setup"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # http://localhost:4321 \u2014 a rota raiz J\xc1 \xc9 o jogo\n"})}),"\n",(0,r.jsx)(o.p,{children:"Opcional:"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"npm run fetch-audio # pacote de \xe1udio (sem ele: sons sintetizados)\n"})}),"\n",(0,r.jsxs)(o.p,{children:["Requisitos: Node 22 (\xe9 o que o CI usa, ",(0,r.jsx)(o.code,{children:".github/workflows/ci.yml:14"}),") e Python 3 para\nparte do arn\xeas (",(0,r.jsx)(o.code,{children:"ref-measure.py"}),", ",(0,r.jsx)(o.code,{children:"char_probe.py"}),", ",(0,r.jsx)(o.code,{children:"mat_shade.py"})," \u2014 usam numpy e PIL)."]}),"\n",(0,r.jsxs)(o.admonition,{type:"caution",children:[(0,r.jsxs)(o.mdxAdmonitionTitle,{children:["Servir ",(0,r.jsx)(o.code,{children:"public/"})," N\xc3O roda o jogo"]}),(0,r.jsxs)(o.p,{children:["N\xe3o existe ",(0,r.jsx)(o.code,{children:"public/index.html"}),": o HTML do jogo \xe9 ",(0,r.jsx)(o.code,{children:"src/pages/index.astro"}),", na rota raiz.\nUse ",(0,r.jsx)(o.code,{children:"npm run dev"}),". Detalhes e prova em\n",(0,r.jsx)(o.a,{href:"/docs/#a-pegadinha-que-custa-a-primeira-hora-de-todo-mundo",children:"Come\xe7ando"}),"."]})]}),"\n",(0,r.jsx)(o.h2,{id:"rodar-o-quality-gate",children:"Rodar o quality gate"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"npm run eval:vm # OBRIGAT\xd3RIO ANTES \u2014 ver o aviso abaixo\nnode tools/eval/invariants.mjs # o quality gate inteiro\nnode tools/eval/invariants.mjs --json # sa\xedda pra m\xe1quina\nnpm run check:fast # syntax + quality gate (r\xe9guas de node puro)\n"})}),"\n",(0,r.jsxs)(o.admonition,{type:"danger",children:[(0,r.jsxs)(o.mdxAdmonitionTitle,{children:[(0,r.jsx)(o.code,{children:"eval:vm"})," roda ANTES de ",(0,r.jsx)(o.code,{children:"invariants.mjs"}),". Sempre."]}),(0,r.jsxs)(o.p,{children:["As invariantes de viewmodel (VM1\u2013VM19) ",(0,r.jsx)(o.strong,{children:"leem"})," ",(0,r.jsx)(o.code,{children:"tools/eval/vm_mint_audit.json"}),", que \xe9 o\n",(0,r.jsx)(o.code,{children:"eval:vm"})," quem ",(0,r.jsx)(o.strong,{children:"escreve"}),". Rodar as invariantes com esse JSON velho mede o viewmodel de\nontem e ",(0,r.jsx)(o.strong,{children:"inventa vermelha"}),": em 04/08/2026 o JSON estava em ",(0,r.jsx)(o.code,{children:"V0=80\xb0"})," contra o ",(0,r.jsx)(o.code,{children:"game.js"}),"\nem ",(0,r.jsx)(o.code,{children:"V0=42\xb0"}),", e a VM5 acusava ",(0,r.jsx)(o.strong,{children:"26/26 armas fora"}),"; depois de ",(0,r.jsx)(o.code,{children:"npm run eval:vm"}),", ",(0,r.jsx)(o.strong,{children:"3/26"}),".\nA VM1 caiu de 26/26 para 2/26 e a VM9 ficou verde."]}),(0,r.jsxs)(o.p,{children:["A ordem do ",(0,r.jsx)(o.code,{children:"npm run check"})," j\xe1 foi corrigida (",(0,r.jsx)(o.code,{children:"package.json"}),") \u2014 o cuidado \xe9 para quando\nvoc\xea chamar ",(0,r.jsx)(o.code,{children:"node tools/eval/invariants.mjs"})," na m\xe3o. Detalhe: BUG-02 do\n",(0,r.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"})}),"."]})]}),"\n",(0,r.jsxs)(o.p,{children:["Custo real: numa m\xe1quina de 2 CPUs, ",(0,r.jsx)(o.strong,{children:"cerca de 10 minutos"}),". Ele sobe o jogo real cinco\nvezes (uma por mapa), roda 60 s de simula\xe7\xe3o de bot por mapa e audita todos os GLBs de arma.\nRode antes de abrir o PR, n\xe3o depois de receber a review."]}),"\n",(0,r.jsx)(o.p,{children:"Arn\xeases individuais, quando voc\xea quiser iterar r\xe1pido numa frente s\xf3:"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"node tools/eval/vm-mint-audit.mjs # enquadramento de viewmodel (todo o arsenal)\nnode tools/eval/vm-solve.mjs # existe ponto vi\xe1vel pras invariantes de VM?\nnode tools/eval/vm-solve.mjs --atual # s\xf3 as margens da config atual (instant\xe2neo)\nnode tools/eval/botsim.mjs 60 all # navega\xe7\xe3o de bots, todos os mapas, sementes fixas\nnode tools/eval/char-probe.mjs # personagens (C1..C6)\nnode tools/eval/map-check.mjs all # geometria de mapa (MAP1-MAP3, CTF1)\nnode tools/eval/mat-check.mjs # material/luz/fog/textura\nnode tools/eval/pickup-check.mjs # todo pickup \xe9 alcan\xe7\xe1vel?\nnode tools/eval/ui-check.mjs # UI1 contraste \xb7 UI2 polui\xe7\xe3o \xb7 UI3 \xe1rea morta \xb7 UI4 ritmo\n"})}),"\n",(0,r.jsx)(o.h3,{id:"antes-de-dizer-que-consertou-mute",children:"Antes de dizer que consertou: mute"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # espera UI1 VERMELHA\n"})}),"\n",(0,r.jsxs)(o.p,{children:["Desfa\xe7a a sua pr\xf3pria corre\xe7\xe3o e confira que o quality gate ",(0,r.jsx)(o.strong,{children:"fica vermelho"}),". Se ficar verde,\no que voc\xea mediu n\xe3o \xe9 o que voc\xea consertou. \xc9 a li\xe7\xe3o mais cara deste reposit\xf3rio e ela\ntem uma p\xe1gina inteira: ",(0,r.jsx)(o.a,{href:"/docs/quality-gates#teste-de-muta%C3%A7%C3%A3o-da-pr%C3%B3pria-r%C3%A9gua",children:"Teste de muta\xe7\xe3o"}),"."]}),"\n",(0,r.jsx)(o.h3,{id:"se-o-seu-pr-\xe9-um-conserto-de-bug",children:"Se o seu PR \xe9 um conserto de bug"}),"\n",(0,r.jsxs)(o.p,{children:["Use a skill ",(0,r.jsx)(o.code,{children:"bug-hunt"})," (",(0,r.jsx)(o.code,{children:".claude/skills/bug-hunt/SKILL.md"}),"). Ela \xe9 o passo a passo desta\ndoutrina aplicado a defeito \u2014 com o caso real que comprou cada regra, o gabarito da entrada\ndo ",(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"})," e o do relat\xf3rio final, incluindo como declarar o que voc\xea ",(0,r.jsx)(o.strong,{children:"n\xe3o"}),"\nverificou. Serve para agente e para gente."]}),"\n",(0,r.jsx)(o.h2,{id:"o-que-um-pr-precisa",children:"O que um PR precisa"}),"\n",(0,r.jsx)(o.h3,{id:"1-uma-invariante-nova--ou-a-raz\xe3o-de-n\xe3o-precisar",children:"1. Uma invariante nova \u2014 ou a raz\xe3o de n\xe3o precisar"}),"\n",(0,r.jsxs)(o.p,{children:["Esta \xe9 a regra que define o projeto. Todo PR que muda ",(0,r.jsx)(o.strong,{children:"comportamento observ\xe1vel"})," traz\numa das duas coisas:"]}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Uma invariante nova"})," em ",(0,r.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),", com teto que tem proced\xeancia\n(arquivo de refer\xeancia + pixel medido + script que reproduz), ou"]}),"\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Uma frase na descri\xe7\xe3o do PR"}),' dizendo por que n\xe3o precisa. Raz\xf5es v\xe1lidas: "j\xe1 \xe9\ncoberto pela invariante X" (diga qual), "\xe9 refatora\xe7\xe3o sem mudan\xe7a observ\xe1vel \u2014 o\nquality gate d\xe1 o mesmo placar antes e depois" (cole os dois), "\xe9 conte\xfado puro (texto,\nasset) sem regra de jogo associada".']}),"\n"]}),"\n",(0,r.jsx)(o.p,{children:'Raz\xe3o inv\xe1lida: "testei manualmente e ficou bom".'}),"\n",(0,r.jsxs)(o.p,{children:["Por qu\xea: ",(0,r.jsx)(o.strong,{children:"inten\xe7\xe3o que n\xe3o vira invariante \xe9 otimizada para fora"}),". Uma rodada levou o\nquality gate de 16/21 para 19/21 sem afrouxar um teto sequer, e foi reprovada, porque destruiu\nem sil\xeancio uma decis\xe3o est\xe9tica que nenhuma invariante codificava. Caso completo em\n",(0,r.jsx)(o.a,{href:"/docs/quality-gates#lei-1--inten%C3%A7%C3%A3o-que-n%C3%A3o-vira-invariante-%C3%A9-otimizada-para-fora",children:"O quality gate"}),"."]}),"\n",(0,r.jsx)(o.h3,{id:"2-o-quality-gate-n\xe3o-pode-piorar",children:"2. O quality gate n\xe3o pode piorar"}),"\n",(0,r.jsxs)(o.p,{children:["Cole a sa\xedda de ",(0,r.jsx)(o.code,{children:"node tools/eval/invariants.mjs"})," antes e depois. Se alguma cr\xedtica ficou\nvermelha, o PR n\xe3o entra. Se voc\xea ",(0,r.jsx)(o.strong,{children:"consertou"})," uma vermelha, diga qual e mostre."]}),"\n",(0,r.jsxs)(o.p,{children:["O estado do quality gate deve ser medido (",(0,r.jsx)(o.a,{href:"/docs/estado",children:"veja como"}),"); a lista viva com causa raiz est\xe1\nno ",(0,r.jsx)(o.code,{children:"KNOWN-BUGS.md"}),"). Isso n\xe3o \xe9 licen\xe7a para piorar: o compromisso \xe9 ",(0,r.jsx)(o.em,{children:'"a sua mudan\xe7a n\xe3o\nacrescenta vermelho"'}),"."]}),"\n",(0,r.jsxs)(o.h3,{id:"3-n\xfameros-com-arquivolinha",children:["3. N\xfameros, com ",(0,r.jsx)(o.code,{children:"arquivo:linha"})]}),"\n",(0,r.jsxs)(o.p,{children:['A afirma\xe7\xe3o "melhorei a ilumina\xe7\xe3o" n\xe3o \xe9 revis\xe1vel. "O ch\xe3o do ',(0,r.jsx)(o.code,{children:"praca_poderes"})," estava 8 pontos\nde L* acima das paredes, causa em ",(0,r.jsx)(o.code,{children:"map_brasilia.js:NNN"}),', corrigido para X" \xe9. Essa\nexig\xeancia n\xe3o \xe9 estilo \u2014 \xe9 o que permite que a pr\xf3xima rodada confira o seu trabalho.']}),"\n",(0,r.jsx)(o.h3,{id:"4-uma-frente-por-pr",children:"4. Uma frente por PR"}),"\n",(0,r.jsxs)(o.p,{children:["Consulte a tabela de conflito (",(0,r.jsx)(o.a,{href:"/docs/arquitetura#a-tabela-de-conflito",children:"Arquitetura"}),"). Um\nPR que toca armas + UI + mapa \xe9 tr\xeas PRs escondidos, e vai colidir com tr\xeas frentes. Em\n",(0,r.jsx)(o.code,{children:"game.js"}),", edite por trecho; nunca sobrescreva o arquivo inteiro."]}),"\n",(0,r.jsx)(o.h3,{id:"5-higiene-do-reposit\xf3rio",children:"5. Higiene do reposit\xf3rio"}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.code,{children:"node --check"})," em cada arquivo de ",(0,r.jsx)(o.code,{children:"public/js/"})," que voc\xea editou (o CI faz isso primeiro,\n",(0,r.jsx)(o.code,{children:".github/workflows/ci.yml:19-20"}),")."]}),"\n",(0,r.jsxs)(o.li,{children:["Coment\xe1rio ",(0,r.jsx)(o.strong,{children:"em portugu\xeas explicando o porqu\xea"}),", n\xe3o o qu\xea. \xc9 a cultura do repo e \xe9 o\nque sobrevive ao pr\xf3ximo handoff."]}),"\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Nunca delete coment\xe1rio de proced\xeancia"})," num PR de limpeza. Aquele par\xe1grafo longo\nexplicando de onde veio o n\xfamero 0,513 \xe9 o que impede a pr\xf3xima rodada de repetir tr\xeas\ndias perdidos."]}),"\n",(0,r.jsxs)(o.li,{children:["Mexeu em ",(0,r.jsx)(o.code,{children:"public/js/*.js"}),"? ",(0,r.jsxs)(o.strong,{children:["Bump o ",(0,r.jsx)(o.code,{children:"?v="})," nos dois lados"]})," \u2014 ",(0,r.jsx)(o.code,{children:"public/js/version.js"})," e o\nimport map de ",(0,r.jsx)(o.code,{children:"src/pages/index.astro"}),'. J\xe1 custou dias de "corre\xe7\xe3o que n\xe3o chegava".']}),"\n",(0,r.jsxs)(o.li,{children:["Mexeu em ",(0,r.jsx)(o.code,{children:"public/js/*.js"}),", no ",(0,r.jsx)(o.code,{children:"maps.js"}),", no ",(0,r.jsx)(o.code,{children:"characters.js"})," ou numa depend\xeancia?\n",(0,r.jsxs)(o.strong,{children:["Rode ",(0,r.jsx)(o.code,{children:"npm run docs"})]})," e commite junto. O ",(0,r.jsx)(o.code,{children:"docs:check"})," est\xe1 no ",(0,r.jsx)(o.code,{children:"check:fast"})," e vai\nreprovar \u2014 leva menos de um segundo e \xe9 o que impede a doc de voltar a mentir."]}),"\n",(0,r.jsxs)(o.li,{children:["Nada de asset com copyright. Nada de ",(0,r.jsx)(o.code,{children:"service_role"})," key commitada."]}),"\n",(0,r.jsx)(o.li,{children:"Nada de depend\xeancia de runtime no jogo. Three.js \xe9 vendorizado; o jogo tem que rodar\narrastando a pasta pra um host est\xe1tico."}),"\n"]}),"\n",(0,r.jsx)(o.h3,{id:"6-linha-editorial",children:"6. Linha editorial"}),"\n",(0,r.jsxs)(o.p,{children:["De ",(0,r.jsx)(o.code,{children:"CONTRIBUTING.md:7-16"}),": o jogo n\xe3o tem lado pol\xedtico (os dois times t\xeam a mesma\nmec\xe2nica), n\xe3o incita \xf3dio, n\xe3o usa pessoas reais \u2014 s\xf3 arqu\xe9tipos originais, sem gore.\nContribui\xe7\xf5es que violem isso s\xe3o recusadas. N\xe3o \xe9 burocracia: \xe9 o que protege o projeto\nde takedown e de virar outra coisa."]}),"\n",(0,r.jsx)(o.h2,{id:"como-adicionar-uma-arma",children:"Como adicionar uma arma"}),"\n",(0,r.jsxs)(o.p,{children:["O pipeline \xe9 data-driven a partir do GLB. Os GLBs de arma vivem em ",(0,r.jsx)(o.code,{children:"public/models/weapons/"}),"\n(a contagem est\xe1 no bloco gerado de ",(0,r.jsx)(o.a,{href:"/docs/",children:"Come\xe7ando"}),")."]}),"\n",(0,r.jsxs)(o.ol,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.strong,{children:"Coloque o GLB"})," em ",(0,r.jsx)(o.code,{children:"public/models/weapons/game.js \u2014 onde o conflito mora",id:"os-maiores-m\xe9todos-de-gamejs--onde-o-conflito-mora",level:3},{value:"Faixas de linha disjuntas",id:"faixas-de-linha-disjuntas",level:2},{value:"Como funciona",id:"como-funciona",level:3},{value:"A tabela de conflito",id:"a-tabela-de-conflito",level:3},{value:"As zonas vermelhas",id:"as-zonas-vermelhas",level:3},{value:"As regras operacionais",id:"as-regras-operacionais",level:3},{value:"As tr\xeas zonas do reposit\xf3rio",id:"as-tr\xeas-zonas-do-reposit\xf3rio",level:2},{value:"Consequ\xeancia pr\xe1tica",id:"consequ\xeancia-pr\xe1tica",level:3},{value:"Sistema de dados de conte\xfado",id:"sistema-de-dados-de-conte\xfado",level:2},{value:"O que \xe9 gerado, e o que n\xe3o \xe9",id:"o-que-\xe9-gerado-e-o-que-n\xe3o-\xe9",level:2},{value:"O que o gerador N\xc3O resolve: ponteiros arquivo:linha na prosa",id:"o-que-o-gerador-n\xe3o-resolve-ponteiros-arquivolinha-na-prosa",level:3}];function t(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"arquitetura-n-agentes-no-mesmo-arquivo",children:"Arquitetura: N agentes no mesmo arquivo"})}),"\n",(0,n.jsx)(o.h2,{id:"por-que-este-documento-\xe9-gerado-por-script",children:"Por que este documento \xe9 gerado por script"}),"\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," n\xe3o \xe9 escrito \xe0 m\xe3o. Ele \xe9 gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-arch.mjs"}),",\ne a raz\xe3o est\xe1 no cabe\xe7alho do script (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:5-8"}),"):"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"ARCH.md"}),' escrito \xe0 m\xe3o dizia "game.js (3234 linhas)" quando o arquivo tinha 5361.\nTodos os ponteiros ',(0,n.jsx)(o.code,{children:"arquivo:linha"})," da tabela de conflito estavam deslocados \u2014 e essa\ntabela \xe9 justamente o que impede dois agentes (ou dois contribuidores) de editarem a\nmesma regi\xe3o. Um \xedndice por n\xfamero de linha escrito \xe0 m\xe3o desatualiza no primeiro\ncommit; a \xfanica corre\xe7\xe3o \xe9 gerar."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["E a separa\xe7\xe3o que faz isso funcionar (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:11-13"}),"):"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"frente -> S\xcdMBOLO = conhecimento humano, est\xe1vel, vive nas FRENTES do script\ns\xedmbolo -> LINHA = vol\xe1til, \xe9 o que este script resolve toda vez\n"})}),"\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"ARCH.md"})," antigo cravava ",(0,n.jsx)(o.strong,{children:"frente \u2192 linha"}),", misturando os dois prazos de validade. \xc9\numa ideia pequena com consequ\xeancia grande: a parti\xe7\xe3o de trabalho \xe9 declarada em termos\nque n\xe3o mudam (nomes de m\xe9todo), e a resolu\xe7\xe3o para coordenadas vol\xe1teis (n\xfameros de\nlinha) \xe9 recalculada a cada execu\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.admonition,{type:"note",children:[(0,n.jsxs)(o.mdxAdmonitionTitle,{children:["O ",(0,n.jsx)(o.code,{children:"arch:check"})," est\xe1 VERMELHO agora \u2014 e isso \xe9 a melhor demonstra\xe7\xe3o da p\xe1gina"]}),(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"npm run arch"})," e ",(0,n.jsx)(o.code,{children:"npm run arch:check"})," existem hoje no ",(0,n.jsx)(o.code,{children:"package.json"})," da raiz, e o cheque\nn\xe3o est\xe1 passando:"]}),(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"$ npm run arch:check\n\u2717 ARCH1 ARCH.md est\xe1 DESATUALIZADO em rela\xe7\xe3o ao c\xf3digo.\n game.js tem 6428 linhas; o \xedndice do ARCH.md n\xe3o bate.\n Rode: npm run arch\n"})}),(0,n.jsxs)(o.p,{children:["A mensagem induz ao erro de prop\xf3sito: ela ",(0,n.jsx)(o.strong,{children:"fala de linhas porque \xe9 o resumo que sabe\nimprimir"}),", mas o que o ",(0,n.jsx)(o.code,{children:"--check"})," compara \xe9 o bloco gerado inteiro, byte a byte \u2014 e esse\nbloco carrega tamb\xe9m o n\xfamero de vers\xe3o do jogo. \xcdndice de s\xedmbolo certo e vers\xe3o velha d\xe1\na mesma vermelha. Um comando resolve."]}),(0,n.jsxs)(o.p,{children:["Cuidado que continua valendo: no CI o passo est\xe1 com ",(0,n.jsx)(o.code,{children:"continue-on-error: true"}),", ent\xe3o o\ncheque roda mas ",(0,n.jsx)(o.strong,{children:"n\xe3o bloqueia"})," \u2014 foi exatamente por isso que ele conseguiu ficar\nvermelho sem que ningu\xe9m percebesse. Tirar essa linha \xe9 o que o transforma em quality gate de\nverdade."]})]}),"\n",(0,n.jsx)(o.h2,{id:"os-arquivos-indexados",children:"Os arquivos indexados"}),"\n",(0,n.jsxs)(o.p,{children:["Tamanho dos arquivos que o ",(0,n.jsx)(o.code,{children:"gen-arch.mjs"})," indexa \u2014 bloco gerado, regenerado por\n",(0,n.jsx)(o.code,{children:"npm run docs"})," e conferido por ",(0,n.jsx)(o.code,{children:"npm run docs:check"}),":"]}),"\n","\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Arquivo"}),(0,n.jsx)(o.th,{style:{textAlign:"right"},children:"Linhas"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/game.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"6.838"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/main.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"2.646"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/characters.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"1.068"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/glbchars.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"837"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/vmattach.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"628"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/weapons.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"344"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/springs.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"260"})]})]})]}),"\n",(0,n.jsxs)(o.p,{children:["Total de ",(0,n.jsx)(o.code,{children:"public/js/"}),": ",(0,n.jsx)(o.strong,{children:"31.744 linhas em 44 arquivos"}),". O \xedndice s\xedmbolo\u2192linha, com a tabela de conflito, \xe9 outro bloco gerado: ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," (",(0,n.jsx)(o.code,{children:"npm run arch"}),")."]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Bloco gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(o.code,{children:"git ls-files public/js/*.js | xargs wc -l"})]}),"\n"]}),"\n","\n",(0,n.jsxs)(o.h3,{id:"os-maiores-m\xe9todos-de-gamejs--onde-o-conflito-mora",children:["Os maiores m\xe9todos de ",(0,n.jsx)(o.code,{children:"game.js"})," \u2014 onde o conflito mora"]}),"\n",(0,n.jsxs)(o.p,{children:["Esta tabela ",(0,n.jsx)(o.strong,{children:"n\xe3o \xe9 reproduzida aqui"}),", e a raz\xe3o \xe9 a pr\xf3pria tese da p\xe1gina: ela \xe9\n",(0,n.jsx)(o.code,{children:"linha \u2192 m\xe9todo"}),", o lado vol\xe1til da separa\xe7\xe3o, e duplic\xe1-la numa p\xe1gina de prosa cria uma\nsegunda c\xf3pia que envelhece sozinha. Ela vive gerada, num lugar s\xf3:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"npm run arch # regenera tools/eval/ARCH.md\nnode tools/gen-arch.mjs --json # o \xedndice cru, para outra ferramenta\n"})}),"\n",(0,n.jsxs)(o.p,{children:["O que ",(0,n.jsx)(o.strong,{children:"n\xe3o"})," envelhece, e por isso fica escrito aqui: ",(0,n.jsx)(o.code,{children:"_updateBot()"})," \xe9 de longe o maior\nm\xe9todo do arquivo e est\xe1 marcado pelo pr\xf3prio \xedndice como ",(0,n.jsx)(o.strong,{children:"candidato a extra\xe7\xe3o"}),";\n",(0,n.jsx)(o.code,{children:"constructor()"}),", ",(0,n.jsx)(o.code,{children:"update()"})," e ",(0,n.jsx)(o.code,{children:"_dom()"})," s\xe3o ",(0,n.jsx)(o.strong,{children:"zona vermelha, append-only"}),", porque qualquer\nfrente pode precisar deles. M\xe9todo grande = PR irrevis\xe1vel e merge conflitante \u2014 extrair\n",(0,n.jsx)(o.code,{children:"_updateBot"})," \xe9 trabalho de valor alto e risco m\xe9dio, e exige coordenar antes, porque a\nregi\xe3o \xe9 disputada."]}),"\n",(0,n.jsx)(o.h2,{id:"faixas-de-linha-disjuntas",children:"Faixas de linha disjuntas"}),"\n",(0,n.jsxs)(o.p,{children:["Este \xe9 o mecanismo que permite v\xe1rios agentes (ou contribuidores) editarem o ",(0,n.jsx)(o.strong,{children:"mesmo\narquivo"})," \u2014 o maior do reposit\xf3rio, com milhares de linhas \u2014 ao mesmo tempo, sem conflito\nde merge."]}),"\n",(0,n.jsx)(o.h3,{id:"como-funciona",children:"Como funciona"}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Cada frente declara S\xcdMBOLOS, nunca linhas."})," Em ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:32-73"}),", a\nconstante ",(0,n.jsx)(o.code,{children:"FRENTES"})," lista, por frente, tr\xeas coisas: ",(0,n.jsx)(o.code,{children:"arquivos"})," exclusivos, ",(0,n.jsx)(o.code,{children:"simbolos"}),"\n(m\xe9todos) e ",(0,n.jsx)(o.code,{children:"consts"})," (constantes de topo). Exemplo, a frente ARMAS/VIEWMODEL possui\n",(0,n.jsx)(o.code,{children:"_buildViewModels"}),", ",(0,n.jsx)(o.code,{children:"_vmFrame"}),", ",(0,n.jsx)(o.code,{children:"_tryShoot"}),", ",(0,n.jsx)(o.code,{children:"_shotRecoil"}),"\u2026 e as constantes ",(0,n.jsx)(o.code,{children:"WEAPONS"}),",\n",(0,n.jsx)(o.code,{children:"VM_FOV_DEFAULT"}),", ",(0,n.jsx)(o.code,{children:"VM_OFF"}),", ",(0,n.jsx)(o.code,{children:"REC_DEG"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O script indexa o arquivo e resolve s\xedmbolo \u2192 faixa."})," ",(0,n.jsx)(o.code,{children:"indexar()"}),"\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:80-111"}),") varre o arquivo linha a linha com tr\xeas padr\xf5es: m\xe9todo\nde classe (exatamente 2 espa\xe7os de indenta\xe7\xe3o), ",(0,n.jsx)(o.strong,{children:"m\xe9todo-arrow atribu\xeddo em runtime"}),"\n(",(0,n.jsx)(o.code,{children:"this._vmFrame = (force) => {"}),") e declara\xe7\xe3o de topo. O fim de cada s\xedmbolo \xe9 o\nin\xedcio do pr\xf3ximo."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Faixas cont\xedguas s\xe3o fundidas"})," (gap \u2264 12 linhas) para a tabela ficar leg\xedvel\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:172-178"}),")."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Sobreposi\xe7\xe3o entre frentes \xe9 detectada"}),", porque uma tabela de conflito que se\ncontradiz \xe9 pior que nenhuma (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:190-200"}),")."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["O detalhe do passo 2 vale destacar: a v1 do script s\xf3 via m\xe9todos de classe, e por isso\n",(0,n.jsx)(o.code,{children:"_vmFrame"})," \u2014 cerca de 100 linhas que nascem ",(0,n.jsx)(o.strong,{children:"dentro"})," de outro m\xe9todo, como arrow que\nfecha sobre vari\xe1veis locais \u2014 ficava ",(0,n.jsx)(o.strong,{children:"invis\xedvel no \xedndice"}),"\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:95-97"}),"). Um \xedndice que n\xe3o v\xea o m\xe9todo mais disputado do arquivo \xe9\npior que nenhum \xedndice, porque d\xe1 falsa confian\xe7a."]}),"\n",(0,n.jsx)(o.h3,{id:"a-tabela-de-conflito",children:"A tabela de conflito"}),"\n",(0,n.jsxs)(o.p,{children:["Do ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," (bloco gerado \u2014 as faixas abaixo s\xe3o as da gera\xe7\xe3o anterior; rode\n",(0,n.jsx)(o.code,{children:"node tools/gen-arch.mjs"})," para as de hoje):"]}),"\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Frente"}),(0,n.jsx)(o.th,{children:"Arquivos exclusivos"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"ARMAS / VIEWMODEL"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"vmattach.js"})," ",(0,n.jsx)(o.code,{children:"springs.js"})," ",(0,n.jsx)(o.code,{children:"weapons.js"})," ",(0,n.jsx)(o.code,{children:"fparms.js"})," ",(0,n.jsx)(o.code,{children:"handik.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"BOTS / JOGABILIDADE"})}),(0,n.jsxs)(o.td,{children:["\u2014 (s\xf3 faixas em ",(0,n.jsx)(o.code,{children:"game.js"}),")"]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"MAPAS / MUNDO"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"maps.js"})," ",(0,n.jsx)(o.code,{children:"mapprops.js"})," ",(0,n.jsx)(o.code,{children:"map_brasilia.js"})," ",(0,n.jsx)(o.code,{children:"map_havan.js"})," ",(0,n.jsx)(o.code,{children:"map_piscina.js"})," ",(0,n.jsx)(o.code,{children:"map_piscinao_ramos.js"})," ",(0,n.jsx)(o.code,{children:"map_ferrovelho.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"GR\xc1FICOS / FX"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"bloom.js"})," ",(0,n.jsx)(o.code,{children:"textures.js"})," ",(0,n.jsx)(o.code,{children:"vao.js"})," ",(0,n.jsx)(o.code,{children:"stylize.js"})," ",(0,n.jsx)(o.code,{children:"gpuparticles.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"UI / HUD / MENU"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"main.js"})," ",(0,n.jsx)(o.code,{children:"public/style.css"})," ",(0,n.jsx)(o.code,{children:"src/pages/index.astro"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"\xc1UDIO"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"audio.js"})})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"PERSONAGENS"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"characters.js"})," ",(0,n.jsx)(o.code,{children:"glbchars.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"SITE / BACKEND"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"src/"})})]})]})]}),"\n",(0,n.jsxs)(o.admonition,{title:"Dois arquivos de mapa N\xc3O t\xeam dono declarado",type:"caution",children:[(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"map_quebrada.js"})," (1.319 linhas, o mapa mais novo) e ",(0,n.jsx)(o.code,{children:"map_decals.js"})," ",(0,n.jsx)(o.strong,{children:"n\xe3o aparecem em\nfrente nenhuma"})," de ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})," \u2014 a lista acima \xe9 c\xf3pia fiel do ",(0,n.jsx)(o.code,{children:"FRENTES"}),", e eles\nn\xe3o est\xe3o l\xe1. Quem editar os dois n\xe3o colide com ningu\xe9m ",(0,n.jsx)(o.em,{children:"segundo a tabela"}),", que \xe9\njustamente a garantia que a tabela deveria dar e n\xe3o d\xe1. Acrescent\xe1-los \xe9 uma linha em\n",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})," seguida de ",(0,n.jsx)(o.code,{children:"npm run arch"}),"."]}),(0,n.jsxs)(o.p,{children:["(O ",(0,n.jsx)(o.code,{children:"map.js"})," j\xe1 foi listado aqui e ",(0,n.jsx)(o.strong,{children:"n\xe3o existe mais"}),': era a "Pra\xe7a (cl\xe1ssico)", apagada\njunto com o mapa ',(0,n.jsx)(o.code,{children:"praca_old"}),".)"]})]}),"\n",(0,n.jsx)(o.h3,{id:"as-zonas-vermelhas",children:"As zonas vermelhas"}),"\n",(0,n.jsxs)(o.p,{children:["Tr\xeas m\xe9todos s\xe3o ",(0,n.jsx)(o.strong,{children:"append-only"}),", porque qualquer frente pode precisar deles\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:75-77"}),"):"]}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"update()"})," \u2014 o loop"]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"_dom()"})," \u2014 o wiring de HUD"]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"constructor()"})," \u2014 um dos maiores m\xe9todos do arquivo (o tamanho de hoje est\xe1 no ",(0,n.jsx)(o.code,{children:"ARCH.md"}),")"]}),"\n"]}),"\n",(0,n.jsx)(o.p,{children:"Editar o miolo destes \xe9 o jeito mais r\xe1pido de dois contribuidores se atropelarem.\nAcrescente no fim; n\xe3o reorganize."}),"\n",(0,n.jsx)(o.h3,{id:"as-regras-operacionais",children:"As regras operacionais"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Declare sua frente antes de editar."})," Se for um PR humano, diga na descri\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsxs)(o.strong,{children:["Em ",(0,n.jsx)(o.code,{children:"game.js"}),", use edi\xe7\xe3o por trecho \u2014 nunca sobrescreva o arquivo inteiro."]})," Uma\nferramenta que reescreve o arquivo apaga o trabalho de quem est\xe1 na outra faixa."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Duas frentes com faixas disjuntas rodam em paralelo."})," O ",(0,n.jsx)(o.code,{children:"ARCH.md"})," gerado registra\nque isso foi medido: ",(0,n.jsx)(o.em,{children:'"3 agentes editaram faixas disjuntas simultaneamente com zero\nconflito de conte\xfado"'})," (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:163"}),")."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Mexeu num s\xedmbolo? Mova o nome na declara\xe7\xe3o da frente, n\xe3o o n\xfamero."})," O script\navisa quando um s\xedmbolo declarado some do c\xf3digo."]}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{title:"Por que isso importa pra voc\xea, humano",type:"tip",children:(0,n.jsxs)(o.p,{children:["A mesma parti\xe7\xe3o que evita colis\xe3o entre agentes \xe9 o que torna um PR seu revis\xe1vel. Um\nPR que toca ",(0,n.jsx)(o.code,{children:"_updateBot"})," + ",(0,n.jsx)(o.code,{children:"style.css"})," + ",(0,n.jsx)(o.code,{children:"map_havan.js"})," \xe9 tr\xeas PRs escondidos num s\xf3, e\nvai colidir com tr\xeas frentes diferentes. Um PR por frente entra r\xe1pido."]})}),"\n",(0,n.jsx)(o.h2,{id:"as-tr\xeas-zonas-do-reposit\xf3rio",children:"As tr\xeas zonas do reposit\xf3rio"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"public/ jogo vanilla ES modules, zero build, Three.js vendorizado\nsrc/ site Astro + adapter Vercel, API routes SSR\ntools/ arn\xeas scripts .mjs/.py \u2014 a r\xe9gua, o quality gate e as sondas\n"})}),"\n",(0,n.jsxs)(o.p,{children:["Vers\xf5es, contagens e o que cada ferramenta faz est\xe3o em\n",(0,n.jsx)(o.a,{href:"/docs/stack",children:"Stack e ferramentas"})," \u2014 ",(0,n.jsx)(o.strong,{children:"gerado"}),", n\xe3o escrito \xe0 m\xe3o."]}),"\n",(0,n.jsx)(o.p,{children:"O acoplamento entre elas \xe9 deliberadamente fino e vale entender:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O site carrega o jogo por import map"}),", em ",(0,n.jsx)(o.code,{children:"src/pages/index.astro:97-123"}),". \xc9 o \xfanico\nponto onde o Astro sabe da exist\xeancia dos m\xf3dulos do jogo."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O arn\xeas carrega o jogo direto do disco"}),", sem browser: ",(0,n.jsx)(o.code,{children:"tools/eval/harness.mjs"})," stuba\nDOM/canvas/",(0,n.jsx)(o.code,{children:"fetch"})," e importa ",(0,n.jsx)(o.code,{children:"public/js/game.js"})," como m\xf3dulo. Por isso o quality gate mede o\nc\xf3digo de produ\xe7\xe3o, e n\xe3o uma reimplementa\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:(0,n.jsx)(o.code,{children:"tools/eval/serve.mjs:15"})})," faz a ponte pro caso de teste: serve ",(0,n.jsx)(o.code,{children:"public/"})," e mapeia\n",(0,n.jsx)(o.code,{children:"/"})," para o fonte do ",(0,n.jsx)(o.code,{children:"index.astro"}),", sem Astro no caminho."]}),"\n"]}),"\n",(0,n.jsx)(o.h3,{id:"consequ\xeancia-pr\xe1tica",children:"Consequ\xeancia pr\xe1tica"}),"\n",(0,n.jsxs)(o.p,{children:["O jogo ",(0,n.jsx)(o.strong,{children:"n\xe3o pode"})," ganhar depend\xeancia de runtime nem passo de build. Isso n\xe3o \xe9\nconservadorismo: \xe9 o que faz ",(0,n.jsx)(o.code,{children:"harness.mjs"})," conseguir subir a classe ",(0,n.jsx)(o.code,{children:"Game"})," em node puro\nem segundos, que \xe9 o que faz o quality gate existir. Um bundler no meio quebraria a r\xe9gua junto\ncom a portabilidade."]}),"\n",(0,n.jsx)(o.h2,{id:"sistema-de-dados-de-conte\xfado",children:"Sistema de dados de conte\xfado"}),"\n",(0,n.jsxs)(o.p,{children:["Hoje mapas, armas e personagens s\xe3o ",(0,n.jsx)(o.strong,{children:"c\xf3digo"}),": cada ",(0,n.jsx)(o.code,{children:"map_*.js"}),' \xe9 geometria declarada \xe0\nm\xe3o, e os maiores deles rivalizam em tamanho com os m\xf3dulos de sistema. A dire\xe7\xe3o\n"conte\xfado como dado" do\n',(0,n.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,n.jsx)(o.code,{children:"docs/ROADMAP.md"})}),"\nquer migrar isso para JSON com loader \xfanico, para que uma contribui\xe7\xe3o de conte\xfado seja\n",(0,n.jsx)(o.em,{children:'"abre um JSON e cria conte\xfado"'})," em vez de ",(0,n.jsx)(o.em,{children:'"um PR de c\xf3digo hand-coded arriscado"'}),"."]}),"\n",(0,n.jsxs)(o.p,{children:["Se voc\xea quer o trabalho de maior alavancagem no projeto inteiro, \xe9 esse. Ver\n",(0,n.jsx)(o.a,{href:"/docs/estado",children:"Estado atual"}),"."]}),"\n",(0,n.jsx)(o.h2,{id:"o-que-\xe9-gerado-e-o-que-n\xe3o-\xe9",children:"O que \xe9 gerado, e o que n\xe3o \xe9"}),"\n",(0,n.jsx)(o.p,{children:"Duas coisas neste reposit\xf3rio s\xe3o geradas por script, e pela mesma raz\xe3o:"}),"\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Gerado"}),(0,n.jsx)(o.th,{children:"Script"}),(0,n.jsx)(o.th,{children:"Quality gate"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," \u2014 \xedndice s\xedmbolo\u2192linha e tabela de conflito"]}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"npm run arch:check"})})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsxs)(o.td,{children:["Os blocos num\xe9ricos de ",(0,n.jsx)(o.code,{children:"README.md"})," e desta documenta\xe7\xe3o"]}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"tools/gen-docs.mjs"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"npm run docs:check"})," (no ",(0,n.jsx)(o.code,{children:"check:fast"}),")"]})]})]})]}),"\n",(0,n.jsx)(o.p,{children:"A regra que separa o que entra e o que fica:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Deriv\xe1vel do c\xf3digo?"})," Vira bloco gerado, entre marcadores, com ",(0,n.jsx)(o.code,{children:"--check"})," no quality gate.\nContagem de linhas, de personagens, de armas, de mapas, de scripts, de invariantes,\nvers\xe3o, lista de scripts do ",(0,n.jsx)(o.code,{children:"package.json"}),", vers\xe3o de depend\xeancia."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"N\xe3o deriv\xe1vel?"})," Ent\xe3o \xe9 decis\xe3o ou explica\xe7\xe3o \u2014 e ",(0,n.jsx)(o.strong,{children:"n\xe3o deve conter n\xfamero que\nenvelhece"}),". Escreva sem o n\xfamero, ou cite o comando que o produz. O placar do quality gate,\npor exemplo, depende de qual insumo existe na m\xe1quina: ele mora colado de uma execu\xe7\xe3o\nreal no ",(0,n.jsx)(o.code,{children:"KNOWN-BUGS.md"}),", n\xe3o repetido em cinco p\xe1ginas."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["E o motivo de o ",(0,n.jsx)(o.code,{children:"--check"})," estar no quality gate, n\xe3o s\xf3 dispon\xedvel: ",(0,n.jsx)(o.strong,{children:"o que n\xe3o vira r\xe9gua \xe9\notimizado para fora."})," Um gerador que ningu\xe9m \xe9 obrigado a rodar desatualiza em uma semana,\ne a\xed a documenta\xe7\xe3o volta a mentir com a apar\xeancia de rigor \u2014 que \xe9 pior do que mentir sem\nela."]}),"\n",(0,n.jsxs)(o.admonition,{title:"Onde voc\xea p\xf5e o quality gate novo na corrente importa",type:"danger",children:[(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"check:fast"})," \xe9 uma corrente de ",(0,n.jsx)(o.code,{children:"&&"}),": o primeiro erro corta o resto. O ",(0,n.jsx)(o.code,{children:"arch:check"})," est\xe1\nvermelho h\xe1 dias, ent\xe3o ",(0,n.jsx)(o.strong,{children:"todo quality gate colocado depois dele nasce morto"})," \u2014 roda zero vezes\ne ningu\xe9m percebe, porque a sa\xedda para antes. Foi exatamente o que aconteceu na primeira\nvers\xe3o do ",(0,n.jsx)(o.code,{children:"docs:check"}),", e \xe9 o mesmo modo de falha do BUG-02 (o quality gate medindo o viewmodel\nde ontem porque o ",(0,n.jsx)(o.code,{children:"&&"})," cortava antes de o JSON ser regenerado)."]}),(0,n.jsxs)(o.p,{children:["Por isso o ",(0,n.jsx)(o.code,{children:"docs:check"})," vem ",(0,n.jsx)(o.strong,{children:"antes"})," do ",(0,n.jsx)(o.code,{children:"arch:check"})," no ",(0,n.jsx)(o.code,{children:"package.json"}),", com o motivo\nescrito no ",(0,n.jsx)(o.code,{children:"SCRIPTS.md"})," (chave ",(0,n.jsx)(o.code,{children:"check:fast"}),"). Quando o ",(0,n.jsx)(o.code,{children:"ARCH.md"})," for regenerado e o ",(0,n.jsx)(o.code,{children:"arch:check"})," voltar\na verde, a ordem deixa de importar; at\xe9 l\xe1, importa."]})]}),"\n",(0,n.jsxs)(o.p,{children:["Colar um bloco novo \xe9 escrever o marcador e rodar ",(0,n.jsx)(o.code,{children:"npm run docs"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"{/* BEGIN:GERADO:NOME_DO_BLOCO \u2014 n\xe3o edite \xe0 m\xe3o, rode `npm run docs` */}\n{/* END:GERADO:NOME_DO_BLOCO */}\n"})}),"\n",(0,n.jsxs)(o.p,{children:["(",(0,n.jsx)(o.code,{children:"NOME_DO_BLOCO"})," \xe9 uma das chaves do objeto ",(0,n.jsx)(o.code,{children:"BLOCOS"})," no topo do ",(0,n.jsx)(o.code,{children:"gen-docs.mjs"}),". Bloco\ndeclarado que ningu\xe9m consome vira aviso alto na sa\xedda \u2014 bloco \xf3rf\xe3o \xe9 c\xf3digo morto que\nfinge ser documenta\xe7\xe3o.)"]}),"\n",(0,n.jsxs)(o.p,{children:["Em Markdown puro (",(0,n.jsx)(o.code,{children:"README.md"}),") o marcador \xe9 coment\xe1rio HTML (",(0,n.jsx)(o.code,{children:"\x3c!-- BEGIN:GERADO:\u2026 --\x3e"}),").\nNas p\xe1ginas desta doc \xe9 coment\xe1rio ",(0,n.jsx)(o.strong,{children:"MDX"})," (",(0,n.jsx)(o.code,{children:"{/* \u2026 */}"}),"): o Docusaurus 3 compila ",(0,n.jsx)(o.code,{children:".md"}),"\ncomo MDX, e coment\xe1rio HTML ali \xe9 erro de parse que derruba o build. O gerador aceita as\nduas sintaxes e preserva a que encontrar."]}),"\n",(0,n.jsxs)(o.h3,{id:"o-que-o-gerador-n\xe3o-resolve-ponteiros-arquivolinha-na-prosa",children:["O que o gerador N\xc3O resolve: ponteiros ",(0,n.jsx)(o.code,{children:"arquivo:linha"})," na prosa"]}),"\n",(0,n.jsxs)(o.p,{children:["Um ",(0,n.jsx)(o.code,{children:"game.js:5361"})," escrito no meio de um par\xe1grafo \xe9 a vers\xe3o barata do mesmo defeito \u2014 ele\naponta pro lugar errado no primeiro commit que mexer no arquivo. N\xe3o d\xe1 pra gerar (o\nponteiro faz parte da frase), mas d\xe1 pra ",(0,n.jsx)(o.strong,{children:"detectar o caso grosseiro"}),": ponteiro que aponta\npara al\xe9m do fim do arquivo."]}),"\n","\n",(0,n.jsxs)(o.p,{children:["Nenhum ponteiro ",(0,n.jsx)(o.code,{children:"arquivo:linha"})," das docs aponta para fora do arquivo que ele cita. \u2713"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Isto confere s\xf3 o ",(0,n.jsx)(o.strong,{children:"limite"})," do arquivo: um ponteiro que ainda cabe mas mudou de assunto passa aqui. \xc9 a raz\xe3o de a doutrina da casa ser declarar o S\xcdMBOLO e deixar a linha para o gerador \u2014 ver ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"}),"."]}),"\n"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Bloco gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(o.code,{children:"varredura de "}),"arquivo",":linha",(0,n.jsx)(o.code,{children:" em README/STATUS/HANDOFF/KNOWN-BUGS/docs/docs/SKILL"})]}),"\n"]}),"\n","\n",(0,n.jsxs)(o.p,{children:["Por isso a doutrina \xe9 declarar o ",(0,n.jsx)(o.strong,{children:"s\xedmbolo"})," e deixar a linha para o gerador. Quando o\n",(0,n.jsx)(o.code,{children:"arquivo:linha"})," for mesmo necess\xe1rio, cite junto o nome do que est\xe1 l\xe1 \u2014 assim quem ler\ndaqui a um m\xeas acha por ",(0,n.jsx)(o.code,{children:"grep"})," mesmo com o ponteiro deslocado."]})]})}function h(e={}){const{wrapper:o}={...(0,d.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(t,{...e})}):t(e)}},8453(e,o,s){s.d(o,{R:()=>a,x:()=>i});var r=s(6540);const n={},d=r.createContext(n);function a(e){const o=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function i(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),r.createElement(d.Provider,{value:o},e.children)}}}]);
\ No newline at end of file
+"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[796],{2616(e,o,s){s.r(o),s.d(o,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"arquitetura","title":"Arquitetura: N agentes no mesmo arquivo","description":"A arquitetura de verdade, gerada por tools/gen-arch.mjs \u2014 e o mecanismo de faixas de linha disjuntas que permite agentes em paralelo sem colis\xe3o.","source":"@site/docs/arquitetura.md","sourceDirName":".","slug":"/arquitetura","permalink":"/docs/arquitetura","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/arquitetura.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"id":"arquitetura","title":"Arquitetura: N agentes no mesmo arquivo","sidebar_label":"Arquitetura","sidebar_position":5,"description":"A arquitetura de verdade, gerada por tools/gen-arch.mjs \u2014 e o mecanismo de faixas de linha disjuntas que permite agentes em paralelo sem colis\xe3o."},"sidebar":"dev","previous":{"title":"BotBrain","permalink":"/docs/botbrain"},"next":{"title":"Como colaborar","permalink":"/docs/colaborar"}}');var n=s(4848),d=s(8453);const a={id:"arquitetura",title:"Arquitetura: N agentes no mesmo arquivo",sidebar_label:"Arquitetura",sidebar_position:5,description:"A arquitetura de verdade, gerada por tools/gen-arch.mjs \u2014 e o mecanismo de faixas de linha disjuntas que permite agentes em paralelo sem colis\xe3o."},i="Arquitetura: N agentes no mesmo arquivo",c={},l=[{value:"Por que este documento \xe9 gerado por script",id:"por-que-este-documento-\xe9-gerado-por-script",level:2},{value:"Os arquivos indexados",id:"os-arquivos-indexados",level:2},{value:"Os maiores m\xe9todos de game.js \u2014 onde o conflito mora",id:"os-maiores-m\xe9todos-de-gamejs--onde-o-conflito-mora",level:3},{value:"Faixas de linha disjuntas",id:"faixas-de-linha-disjuntas",level:2},{value:"Como funciona",id:"como-funciona",level:3},{value:"A tabela de conflito",id:"a-tabela-de-conflito",level:3},{value:"As zonas vermelhas",id:"as-zonas-vermelhas",level:3},{value:"As regras operacionais",id:"as-regras-operacionais",level:3},{value:"As tr\xeas zonas do reposit\xf3rio",id:"as-tr\xeas-zonas-do-reposit\xf3rio",level:2},{value:"Consequ\xeancia pr\xe1tica",id:"consequ\xeancia-pr\xe1tica",level:3},{value:"Sistema de dados de conte\xfado",id:"sistema-de-dados-de-conte\xfado",level:2},{value:"O que \xe9 gerado, e o que n\xe3o \xe9",id:"o-que-\xe9-gerado-e-o-que-n\xe3o-\xe9",level:2},{value:"O que o gerador N\xc3O resolve: ponteiros arquivo:linha na prosa",id:"o-que-o-gerador-n\xe3o-resolve-ponteiros-arquivolinha-na-prosa",level:3}];function t(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"arquitetura-n-agentes-no-mesmo-arquivo",children:"Arquitetura: N agentes no mesmo arquivo"})}),"\n",(0,n.jsx)(o.h2,{id:"por-que-este-documento-\xe9-gerado-por-script",children:"Por que este documento \xe9 gerado por script"}),"\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," n\xe3o \xe9 escrito \xe0 m\xe3o. Ele \xe9 gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-arch.mjs"}),",\ne a raz\xe3o est\xe1 no cabe\xe7alho do script (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:5-8"}),"):"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"ARCH.md"}),' escrito \xe0 m\xe3o dizia "game.js (3234 linhas)" quando o arquivo tinha 5361.\nTodos os ponteiros ',(0,n.jsx)(o.code,{children:"arquivo:linha"})," da tabela de conflito estavam deslocados \u2014 e essa\ntabela \xe9 justamente o que impede dois agentes (ou dois contribuidores) de editarem a\nmesma regi\xe3o. Um \xedndice por n\xfamero de linha escrito \xe0 m\xe3o desatualiza no primeiro\ncommit; a \xfanica corre\xe7\xe3o \xe9 gerar."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["E a separa\xe7\xe3o que faz isso funcionar (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:11-13"}),"):"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"frente -> S\xcdMBOLO = conhecimento humano, est\xe1vel, vive nas FRENTES do script\ns\xedmbolo -> LINHA = vol\xe1til, \xe9 o que este script resolve toda vez\n"})}),"\n",(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"ARCH.md"})," antigo cravava ",(0,n.jsx)(o.strong,{children:"frente \u2192 linha"}),", misturando os dois prazos de validade. \xc9\numa ideia pequena com consequ\xeancia grande: a parti\xe7\xe3o de trabalho \xe9 declarada em termos\nque n\xe3o mudam (nomes de m\xe9todo), e a resolu\xe7\xe3o para coordenadas vol\xe1teis (n\xfameros de\nlinha) \xe9 recalculada a cada execu\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.admonition,{type:"note",children:[(0,n.jsxs)(o.mdxAdmonitionTitle,{children:["O ",(0,n.jsx)(o.code,{children:"arch:check"})," est\xe1 VERMELHO agora \u2014 e isso \xe9 a melhor demonstra\xe7\xe3o da p\xe1gina"]}),(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"npm run arch"})," e ",(0,n.jsx)(o.code,{children:"npm run arch:check"})," existem hoje no ",(0,n.jsx)(o.code,{children:"package.json"})," da raiz, e o cheque\nn\xe3o est\xe1 passando:"]}),(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"$ npm run arch:check\n\u2717 ARCH1 ARCH.md est\xe1 DESATUALIZADO em rela\xe7\xe3o ao c\xf3digo.\n game.js tem 6428 linhas; o \xedndice do ARCH.md n\xe3o bate.\n Rode: npm run arch\n"})}),(0,n.jsxs)(o.p,{children:["A mensagem induz ao erro de prop\xf3sito: ela ",(0,n.jsx)(o.strong,{children:"fala de linhas porque \xe9 o resumo que sabe\nimprimir"}),", mas o que o ",(0,n.jsx)(o.code,{children:"--check"})," compara \xe9 o bloco gerado inteiro, byte a byte \u2014 e esse\nbloco carrega tamb\xe9m o n\xfamero de vers\xe3o do jogo. \xcdndice de s\xedmbolo certo e vers\xe3o velha d\xe1\na mesma vermelha. Um comando resolve."]}),(0,n.jsxs)(o.p,{children:["Cuidado que continua valendo: no CI o passo est\xe1 com ",(0,n.jsx)(o.code,{children:"continue-on-error: true"}),", ent\xe3o o\ncheque roda mas ",(0,n.jsx)(o.strong,{children:"n\xe3o bloqueia"})," \u2014 foi exatamente por isso que ele conseguiu ficar\nvermelho sem que ningu\xe9m percebesse. Tirar essa linha \xe9 o que o transforma em quality gate de\nverdade."]})]}),"\n",(0,n.jsx)(o.h2,{id:"os-arquivos-indexados",children:"Os arquivos indexados"}),"\n",(0,n.jsxs)(o.p,{children:["Tamanho dos arquivos que o ",(0,n.jsx)(o.code,{children:"gen-arch.mjs"})," indexa \u2014 bloco gerado, regenerado por\n",(0,n.jsx)(o.code,{children:"npm run docs"})," e conferido por ",(0,n.jsx)(o.code,{children:"npm run docs:check"}),":"]}),"\n","\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Arquivo"}),(0,n.jsx)(o.th,{style:{textAlign:"right"},children:"Linhas"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/game.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"6.910"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/main.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"2.698"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/characters.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"1.068"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/glbchars.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"844"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/vmattach.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"628"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/weapons.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"346"})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"public/js/springs.js"})}),(0,n.jsx)(o.td,{style:{textAlign:"right"},children:"260"})]})]})]}),"\n",(0,n.jsxs)(o.p,{children:["Total de ",(0,n.jsx)(o.code,{children:"public/js/"}),": ",(0,n.jsx)(o.strong,{children:"32.001 linhas em 44 arquivos"}),". O \xedndice s\xedmbolo\u2192linha, com a tabela de conflito, \xe9 outro bloco gerado: ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," (",(0,n.jsx)(o.code,{children:"npm run arch"}),")."]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Bloco gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(o.code,{children:"git ls-files public/js/*.js | xargs wc -l"})]}),"\n"]}),"\n","\n",(0,n.jsxs)(o.h3,{id:"os-maiores-m\xe9todos-de-gamejs--onde-o-conflito-mora",children:["Os maiores m\xe9todos de ",(0,n.jsx)(o.code,{children:"game.js"})," \u2014 onde o conflito mora"]}),"\n",(0,n.jsxs)(o.p,{children:["Esta tabela ",(0,n.jsx)(o.strong,{children:"n\xe3o \xe9 reproduzida aqui"}),", e a raz\xe3o \xe9 a pr\xf3pria tese da p\xe1gina: ela \xe9\n",(0,n.jsx)(o.code,{children:"linha \u2192 m\xe9todo"}),", o lado vol\xe1til da separa\xe7\xe3o, e duplic\xe1-la numa p\xe1gina de prosa cria uma\nsegunda c\xf3pia que envelhece sozinha. Ela vive gerada, num lugar s\xf3:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"npm run arch # regenera tools/eval/ARCH.md\nnode tools/gen-arch.mjs --json # o \xedndice cru, para outra ferramenta\n"})}),"\n",(0,n.jsxs)(o.p,{children:["O que ",(0,n.jsx)(o.strong,{children:"n\xe3o"})," envelhece, e por isso fica escrito aqui: ",(0,n.jsx)(o.code,{children:"_updateBot()"})," \xe9 de longe o maior\nm\xe9todo do arquivo e est\xe1 marcado pelo pr\xf3prio \xedndice como ",(0,n.jsx)(o.strong,{children:"candidato a extra\xe7\xe3o"}),";\n",(0,n.jsx)(o.code,{children:"constructor()"}),", ",(0,n.jsx)(o.code,{children:"update()"})," e ",(0,n.jsx)(o.code,{children:"_dom()"})," s\xe3o ",(0,n.jsx)(o.strong,{children:"zona vermelha, append-only"}),", porque qualquer\nfrente pode precisar deles. M\xe9todo grande = PR irrevis\xe1vel e merge conflitante \u2014 extrair\n",(0,n.jsx)(o.code,{children:"_updateBot"})," \xe9 trabalho de valor alto e risco m\xe9dio, e exige coordenar antes, porque a\nregi\xe3o \xe9 disputada."]}),"\n",(0,n.jsx)(o.h2,{id:"faixas-de-linha-disjuntas",children:"Faixas de linha disjuntas"}),"\n",(0,n.jsxs)(o.p,{children:["Este \xe9 o mecanismo que permite v\xe1rios agentes (ou contribuidores) editarem o ",(0,n.jsx)(o.strong,{children:"mesmo\narquivo"})," \u2014 o maior do reposit\xf3rio, com milhares de linhas \u2014 ao mesmo tempo, sem conflito\nde merge."]}),"\n",(0,n.jsx)(o.h3,{id:"como-funciona",children:"Como funciona"}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Cada frente declara S\xcdMBOLOS, nunca linhas."})," Em ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:32-73"}),", a\nconstante ",(0,n.jsx)(o.code,{children:"FRENTES"})," lista, por frente, tr\xeas coisas: ",(0,n.jsx)(o.code,{children:"arquivos"})," exclusivos, ",(0,n.jsx)(o.code,{children:"simbolos"}),"\n(m\xe9todos) e ",(0,n.jsx)(o.code,{children:"consts"})," (constantes de topo). Exemplo, a frente ARMAS/VIEWMODEL possui\n",(0,n.jsx)(o.code,{children:"_buildViewModels"}),", ",(0,n.jsx)(o.code,{children:"_vmFrame"}),", ",(0,n.jsx)(o.code,{children:"_tryShoot"}),", ",(0,n.jsx)(o.code,{children:"_shotRecoil"}),"\u2026 e as constantes ",(0,n.jsx)(o.code,{children:"WEAPONS"}),",\n",(0,n.jsx)(o.code,{children:"VM_FOV_DEFAULT"}),", ",(0,n.jsx)(o.code,{children:"VM_OFF"}),", ",(0,n.jsx)(o.code,{children:"REC_DEG"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O script indexa o arquivo e resolve s\xedmbolo \u2192 faixa."})," ",(0,n.jsx)(o.code,{children:"indexar()"}),"\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:80-111"}),") varre o arquivo linha a linha com tr\xeas padr\xf5es: m\xe9todo\nde classe (exatamente 2 espa\xe7os de indenta\xe7\xe3o), ",(0,n.jsx)(o.strong,{children:"m\xe9todo-arrow atribu\xeddo em runtime"}),"\n(",(0,n.jsx)(o.code,{children:"this._vmFrame = (force) => {"}),") e declara\xe7\xe3o de topo. O fim de cada s\xedmbolo \xe9 o\nin\xedcio do pr\xf3ximo."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Faixas cont\xedguas s\xe3o fundidas"})," (gap \u2264 12 linhas) para a tabela ficar leg\xedvel\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:172-178"}),")."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Sobreposi\xe7\xe3o entre frentes \xe9 detectada"}),", porque uma tabela de conflito que se\ncontradiz \xe9 pior que nenhuma (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:190-200"}),")."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["O detalhe do passo 2 vale destacar: a v1 do script s\xf3 via m\xe9todos de classe, e por isso\n",(0,n.jsx)(o.code,{children:"_vmFrame"})," \u2014 cerca de 100 linhas que nascem ",(0,n.jsx)(o.strong,{children:"dentro"})," de outro m\xe9todo, como arrow que\nfecha sobre vari\xe1veis locais \u2014 ficava ",(0,n.jsx)(o.strong,{children:"invis\xedvel no \xedndice"}),"\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:95-97"}),"). Um \xedndice que n\xe3o v\xea o m\xe9todo mais disputado do arquivo \xe9\npior que nenhum \xedndice, porque d\xe1 falsa confian\xe7a."]}),"\n",(0,n.jsx)(o.h3,{id:"a-tabela-de-conflito",children:"A tabela de conflito"}),"\n",(0,n.jsxs)(o.p,{children:["Do ",(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," (bloco gerado \u2014 as faixas abaixo s\xe3o as da gera\xe7\xe3o anterior; rode\n",(0,n.jsx)(o.code,{children:"node tools/gen-arch.mjs"})," para as de hoje):"]}),"\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Frente"}),(0,n.jsx)(o.th,{children:"Arquivos exclusivos"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"ARMAS / VIEWMODEL"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"vmattach.js"})," ",(0,n.jsx)(o.code,{children:"springs.js"})," ",(0,n.jsx)(o.code,{children:"weapons.js"})," ",(0,n.jsx)(o.code,{children:"fparms.js"})," ",(0,n.jsx)(o.code,{children:"handik.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"BOTS / JOGABILIDADE"})}),(0,n.jsxs)(o.td,{children:["\u2014 (s\xf3 faixas em ",(0,n.jsx)(o.code,{children:"game.js"}),")"]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"MAPAS / MUNDO"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"maps.js"})," ",(0,n.jsx)(o.code,{children:"mapprops.js"})," ",(0,n.jsx)(o.code,{children:"map_brasilia.js"})," ",(0,n.jsx)(o.code,{children:"map_havan.js"})," ",(0,n.jsx)(o.code,{children:"map_piscina.js"})," ",(0,n.jsx)(o.code,{children:"map_piscinao_ramos.js"})," ",(0,n.jsx)(o.code,{children:"map_ferrovelho.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"GR\xc1FICOS / FX"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"bloom.js"})," ",(0,n.jsx)(o.code,{children:"textures.js"})," ",(0,n.jsx)(o.code,{children:"vao.js"})," ",(0,n.jsx)(o.code,{children:"stylize.js"})," ",(0,n.jsx)(o.code,{children:"gpuparticles.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"UI / HUD / MENU"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"main.js"})," ",(0,n.jsx)(o.code,{children:"public/style.css"})," ",(0,n.jsx)(o.code,{children:"src/pages/index.astro"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"\xc1UDIO"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"audio.js"})})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"PERSONAGENS"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"characters.js"})," ",(0,n.jsx)(o.code,{children:"glbchars.js"})]})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.td,{children:(0,n.jsx)(o.strong,{children:"SITE / BACKEND"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"src/"})})]})]})]}),"\n",(0,n.jsxs)(o.admonition,{title:"Dois arquivos de mapa N\xc3O t\xeam dono declarado",type:"caution",children:[(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"map_quebrada.js"})," (1.319 linhas, o mapa mais novo) e ",(0,n.jsx)(o.code,{children:"map_decals.js"})," ",(0,n.jsx)(o.strong,{children:"n\xe3o aparecem em\nfrente nenhuma"})," de ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})," \u2014 a lista acima \xe9 c\xf3pia fiel do ",(0,n.jsx)(o.code,{children:"FRENTES"}),", e eles\nn\xe3o est\xe3o l\xe1. Quem editar os dois n\xe3o colide com ningu\xe9m ",(0,n.jsx)(o.em,{children:"segundo a tabela"}),", que \xe9\njustamente a garantia que a tabela deveria dar e n\xe3o d\xe1. Acrescent\xe1-los \xe9 uma linha em\n",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})," seguida de ",(0,n.jsx)(o.code,{children:"npm run arch"}),"."]}),(0,n.jsxs)(o.p,{children:["(O ",(0,n.jsx)(o.code,{children:"map.js"})," j\xe1 foi listado aqui e ",(0,n.jsx)(o.strong,{children:"n\xe3o existe mais"}),': era a "Pra\xe7a (cl\xe1ssico)", apagada\njunto com o mapa ',(0,n.jsx)(o.code,{children:"praca_old"}),".)"]})]}),"\n",(0,n.jsx)(o.h3,{id:"as-zonas-vermelhas",children:"As zonas vermelhas"}),"\n",(0,n.jsxs)(o.p,{children:["Tr\xeas m\xe9todos s\xe3o ",(0,n.jsx)(o.strong,{children:"append-only"}),", porque qualquer frente pode precisar deles\n(",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:75-77"}),"):"]}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"update()"})," \u2014 o loop"]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"_dom()"})," \u2014 o wiring de HUD"]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"constructor()"})," \u2014 um dos maiores m\xe9todos do arquivo (o tamanho de hoje est\xe1 no ",(0,n.jsx)(o.code,{children:"ARCH.md"}),")"]}),"\n"]}),"\n",(0,n.jsx)(o.p,{children:"Editar o miolo destes \xe9 o jeito mais r\xe1pido de dois contribuidores se atropelarem.\nAcrescente no fim; n\xe3o reorganize."}),"\n",(0,n.jsx)(o.h3,{id:"as-regras-operacionais",children:"As regras operacionais"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Declare sua frente antes de editar."})," Se for um PR humano, diga na descri\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsxs)(o.strong,{children:["Em ",(0,n.jsx)(o.code,{children:"game.js"}),", use edi\xe7\xe3o por trecho \u2014 nunca sobrescreva o arquivo inteiro."]})," Uma\nferramenta que reescreve o arquivo apaga o trabalho de quem est\xe1 na outra faixa."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Duas frentes com faixas disjuntas rodam em paralelo."})," O ",(0,n.jsx)(o.code,{children:"ARCH.md"})," gerado registra\nque isso foi medido: ",(0,n.jsx)(o.em,{children:'"3 agentes editaram faixas disjuntas simultaneamente com zero\nconflito de conte\xfado"'})," (",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs:163"}),")."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Mexeu num s\xedmbolo? Mova o nome na declara\xe7\xe3o da frente, n\xe3o o n\xfamero."})," O script\navisa quando um s\xedmbolo declarado some do c\xf3digo."]}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{title:"Por que isso importa pra voc\xea, humano",type:"tip",children:(0,n.jsxs)(o.p,{children:["A mesma parti\xe7\xe3o que evita colis\xe3o entre agentes \xe9 o que torna um PR seu revis\xe1vel. Um\nPR que toca ",(0,n.jsx)(o.code,{children:"_updateBot"})," + ",(0,n.jsx)(o.code,{children:"style.css"})," + ",(0,n.jsx)(o.code,{children:"map_havan.js"})," \xe9 tr\xeas PRs escondidos num s\xf3, e\nvai colidir com tr\xeas frentes diferentes. Um PR por frente entra r\xe1pido."]})}),"\n",(0,n.jsx)(o.h2,{id:"as-tr\xeas-zonas-do-reposit\xf3rio",children:"As tr\xeas zonas do reposit\xf3rio"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"public/ jogo vanilla ES modules, zero build, Three.js vendorizado\nsrc/ site Astro + adapter Vercel, API routes SSR\ntools/ arn\xeas scripts .mjs/.py \u2014 a r\xe9gua, o quality gate e as sondas\n"})}),"\n",(0,n.jsxs)(o.p,{children:["Vers\xf5es, contagens e o que cada ferramenta faz est\xe3o em\n",(0,n.jsx)(o.a,{href:"/docs/stack",children:"Stack e ferramentas"})," \u2014 ",(0,n.jsx)(o.strong,{children:"gerado"}),", n\xe3o escrito \xe0 m\xe3o."]}),"\n",(0,n.jsx)(o.p,{children:"O acoplamento entre elas \xe9 deliberadamente fino e vale entender:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O site carrega o jogo por import map"}),", em ",(0,n.jsx)(o.code,{children:"src/pages/index.astro:97-123"}),". \xc9 o \xfanico\nponto onde o Astro sabe da exist\xeancia dos m\xf3dulos do jogo."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"O arn\xeas carrega o jogo direto do disco"}),", sem browser: ",(0,n.jsx)(o.code,{children:"tools/eval/harness.mjs"})," stuba\nDOM/canvas/",(0,n.jsx)(o.code,{children:"fetch"})," e importa ",(0,n.jsx)(o.code,{children:"public/js/game.js"})," como m\xf3dulo. Por isso o quality gate mede o\nc\xf3digo de produ\xe7\xe3o, e n\xe3o uma reimplementa\xe7\xe3o."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:(0,n.jsx)(o.code,{children:"tools/eval/serve.mjs:15"})})," faz a ponte pro caso de teste: serve ",(0,n.jsx)(o.code,{children:"public/"})," e mapeia\n",(0,n.jsx)(o.code,{children:"/"})," para o fonte do ",(0,n.jsx)(o.code,{children:"index.astro"}),", sem Astro no caminho."]}),"\n"]}),"\n",(0,n.jsx)(o.h3,{id:"consequ\xeancia-pr\xe1tica",children:"Consequ\xeancia pr\xe1tica"}),"\n",(0,n.jsxs)(o.p,{children:["O jogo ",(0,n.jsx)(o.strong,{children:"n\xe3o pode"})," ganhar depend\xeancia de runtime nem passo de build. Isso n\xe3o \xe9\nconservadorismo: \xe9 o que faz ",(0,n.jsx)(o.code,{children:"harness.mjs"})," conseguir subir a classe ",(0,n.jsx)(o.code,{children:"Game"})," em node puro\nem segundos, que \xe9 o que faz o quality gate existir. Um bundler no meio quebraria a r\xe9gua junto\ncom a portabilidade."]}),"\n",(0,n.jsx)(o.h2,{id:"sistema-de-dados-de-conte\xfado",children:"Sistema de dados de conte\xfado"}),"\n",(0,n.jsxs)(o.p,{children:["Hoje mapas, armas e personagens s\xe3o ",(0,n.jsx)(o.strong,{children:"c\xf3digo"}),": cada ",(0,n.jsx)(o.code,{children:"map_*.js"}),' \xe9 geometria declarada \xe0\nm\xe3o, e os maiores deles rivalizam em tamanho com os m\xf3dulos de sistema. A dire\xe7\xe3o\n"conte\xfado como dado" do\n',(0,n.jsx)(o.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,n.jsx)(o.code,{children:"docs/ROADMAP.md"})}),"\nquer migrar isso para JSON com loader \xfanico, para que uma contribui\xe7\xe3o de conte\xfado seja\n",(0,n.jsx)(o.em,{children:'"abre um JSON e cria conte\xfado"'})," em vez de ",(0,n.jsx)(o.em,{children:'"um PR de c\xf3digo hand-coded arriscado"'}),"."]}),"\n",(0,n.jsxs)(o.p,{children:["Se voc\xea quer o trabalho de maior alavancagem no projeto inteiro, \xe9 esse. Ver\n",(0,n.jsx)(o.a,{href:"/docs/estado",children:"Estado atual"}),"."]}),"\n",(0,n.jsx)(o.h2,{id:"o-que-\xe9-gerado-e-o-que-n\xe3o-\xe9",children:"O que \xe9 gerado, e o que n\xe3o \xe9"}),"\n",(0,n.jsx)(o.p,{children:"Duas coisas neste reposit\xf3rio s\xe3o geradas por script, e pela mesma raz\xe3o:"}),"\n",(0,n.jsxs)(o.table,{children:[(0,n.jsx)(o.thead,{children:(0,n.jsxs)(o.tr,{children:[(0,n.jsx)(o.th,{children:"Gerado"}),(0,n.jsx)(o.th,{children:"Script"}),(0,n.jsx)(o.th,{children:"Quality gate"})]})}),(0,n.jsxs)(o.tbody,{children:[(0,n.jsxs)(o.tr,{children:[(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"tools/eval/ARCH.md"})," \u2014 \xedndice s\xedmbolo\u2192linha e tabela de conflito"]}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"})}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"npm run arch:check"})})]}),(0,n.jsxs)(o.tr,{children:[(0,n.jsxs)(o.td,{children:["Os blocos num\xe9ricos de ",(0,n.jsx)(o.code,{children:"README.md"})," e desta documenta\xe7\xe3o"]}),(0,n.jsx)(o.td,{children:(0,n.jsx)(o.code,{children:"tools/gen-docs.mjs"})}),(0,n.jsxs)(o.td,{children:[(0,n.jsx)(o.code,{children:"npm run docs:check"})," (no ",(0,n.jsx)(o.code,{children:"check:fast"}),")"]})]})]})]}),"\n",(0,n.jsx)(o.p,{children:"A regra que separa o que entra e o que fica:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"Deriv\xe1vel do c\xf3digo?"})," Vira bloco gerado, entre marcadores, com ",(0,n.jsx)(o.code,{children:"--check"})," no quality gate.\nContagem de linhas, de personagens, de armas, de mapas, de scripts, de invariantes,\nvers\xe3o, lista de scripts do ",(0,n.jsx)(o.code,{children:"package.json"}),", vers\xe3o de depend\xeancia."]}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.strong,{children:"N\xe3o deriv\xe1vel?"})," Ent\xe3o \xe9 decis\xe3o ou explica\xe7\xe3o \u2014 e ",(0,n.jsx)(o.strong,{children:"n\xe3o deve conter n\xfamero que\nenvelhece"}),". Escreva sem o n\xfamero, ou cite o comando que o produz. O placar do quality gate,\npor exemplo, depende de qual insumo existe na m\xe1quina: ele mora colado de uma execu\xe7\xe3o\nreal no ",(0,n.jsx)(o.code,{children:"KNOWN-BUGS.md"}),", n\xe3o repetido em cinco p\xe1ginas."]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["E o motivo de o ",(0,n.jsx)(o.code,{children:"--check"})," estar no quality gate, n\xe3o s\xf3 dispon\xedvel: ",(0,n.jsx)(o.strong,{children:"o que n\xe3o vira r\xe9gua \xe9\notimizado para fora."})," Um gerador que ningu\xe9m \xe9 obrigado a rodar desatualiza em uma semana,\ne a\xed a documenta\xe7\xe3o volta a mentir com a apar\xeancia de rigor \u2014 que \xe9 pior do que mentir sem\nela."]}),"\n",(0,n.jsxs)(o.admonition,{title:"Onde voc\xea p\xf5e o quality gate novo na corrente importa",type:"danger",children:[(0,n.jsxs)(o.p,{children:["O ",(0,n.jsx)(o.code,{children:"check:fast"})," \xe9 uma corrente de ",(0,n.jsx)(o.code,{children:"&&"}),": o primeiro erro corta o resto. O ",(0,n.jsx)(o.code,{children:"arch:check"})," est\xe1\nvermelho h\xe1 dias, ent\xe3o ",(0,n.jsx)(o.strong,{children:"todo quality gate colocado depois dele nasce morto"})," \u2014 roda zero vezes\ne ningu\xe9m percebe, porque a sa\xedda para antes. Foi exatamente o que aconteceu na primeira\nvers\xe3o do ",(0,n.jsx)(o.code,{children:"docs:check"}),", e \xe9 o mesmo modo de falha do BUG-02 (o quality gate medindo o viewmodel\nde ontem porque o ",(0,n.jsx)(o.code,{children:"&&"})," cortava antes de o JSON ser regenerado)."]}),(0,n.jsxs)(o.p,{children:["Por isso o ",(0,n.jsx)(o.code,{children:"docs:check"})," vem ",(0,n.jsx)(o.strong,{children:"antes"})," do ",(0,n.jsx)(o.code,{children:"arch:check"})," no ",(0,n.jsx)(o.code,{children:"package.json"}),", com o motivo\nescrito no ",(0,n.jsx)(o.code,{children:"SCRIPTS.md"})," (chave ",(0,n.jsx)(o.code,{children:"check:fast"}),"). Quando o ",(0,n.jsx)(o.code,{children:"ARCH.md"})," for regenerado e o ",(0,n.jsx)(o.code,{children:"arch:check"})," voltar\na verde, a ordem deixa de importar; at\xe9 l\xe1, importa."]})]}),"\n",(0,n.jsxs)(o.p,{children:["Colar um bloco novo \xe9 escrever o marcador e rodar ",(0,n.jsx)(o.code,{children:"npm run docs"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"{/* BEGIN:GERADO:NOME_DO_BLOCO \u2014 n\xe3o edite \xe0 m\xe3o, rode `npm run docs` */}\n{/* END:GERADO:NOME_DO_BLOCO */}\n"})}),"\n",(0,n.jsxs)(o.p,{children:["(",(0,n.jsx)(o.code,{children:"NOME_DO_BLOCO"})," \xe9 uma das chaves do objeto ",(0,n.jsx)(o.code,{children:"BLOCOS"})," no topo do ",(0,n.jsx)(o.code,{children:"gen-docs.mjs"}),". Bloco\ndeclarado que ningu\xe9m consome vira aviso alto na sa\xedda \u2014 bloco \xf3rf\xe3o \xe9 c\xf3digo morto que\nfinge ser documenta\xe7\xe3o.)"]}),"\n",(0,n.jsxs)(o.p,{children:["Em Markdown puro (",(0,n.jsx)(o.code,{children:"README.md"}),") o marcador \xe9 coment\xe1rio HTML (",(0,n.jsx)(o.code,{children:"\x3c!-- BEGIN:GERADO:\u2026 --\x3e"}),").\nNas p\xe1ginas desta doc \xe9 coment\xe1rio ",(0,n.jsx)(o.strong,{children:"MDX"})," (",(0,n.jsx)(o.code,{children:"{/* \u2026 */}"}),"): o Docusaurus 3 compila ",(0,n.jsx)(o.code,{children:".md"}),"\ncomo MDX, e coment\xe1rio HTML ali \xe9 erro de parse que derruba o build. O gerador aceita as\nduas sintaxes e preserva a que encontrar."]}),"\n",(0,n.jsxs)(o.h3,{id:"o-que-o-gerador-n\xe3o-resolve-ponteiros-arquivolinha-na-prosa",children:["O que o gerador N\xc3O resolve: ponteiros ",(0,n.jsx)(o.code,{children:"arquivo:linha"})," na prosa"]}),"\n",(0,n.jsxs)(o.p,{children:["Um ",(0,n.jsx)(o.code,{children:"game.js:5361"})," escrito no meio de um par\xe1grafo \xe9 a vers\xe3o barata do mesmo defeito \u2014 ele\naponta pro lugar errado no primeiro commit que mexer no arquivo. N\xe3o d\xe1 pra gerar (o\nponteiro faz parte da frase), mas d\xe1 pra ",(0,n.jsx)(o.strong,{children:"detectar o caso grosseiro"}),": ponteiro que aponta\npara al\xe9m do fim do arquivo."]}),"\n","\n",(0,n.jsxs)(o.p,{children:["Nenhum ponteiro ",(0,n.jsx)(o.code,{children:"arquivo:linha"})," das docs aponta para fora do arquivo que ele cita. \u2713"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Isto confere s\xf3 o ",(0,n.jsx)(o.strong,{children:"limite"})," do arquivo: um ponteiro que ainda cabe mas mudou de assunto passa aqui. \xc9 a raz\xe3o de a doutrina da casa ser declarar o S\xcdMBOLO e deixar a linha para o gerador \u2014 ver ",(0,n.jsx)(o.code,{children:"tools/gen-arch.mjs"}),"."]}),"\n"]}),"\n",(0,n.jsxs)(o.blockquote,{children:["\n",(0,n.jsxs)(o.p,{children:["Bloco gerado por ",(0,n.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(o.code,{children:"varredura de "}),"arquivo",":linha",(0,n.jsx)(o.code,{children:" em README/STATUS/HANDOFF/KNOWN-BUGS/docs/docs/SKILL"})]}),"\n"]}),"\n","\n",(0,n.jsxs)(o.p,{children:["Por isso a doutrina \xe9 declarar o ",(0,n.jsx)(o.strong,{children:"s\xedmbolo"})," e deixar a linha para o gerador. Quando o\n",(0,n.jsx)(o.code,{children:"arquivo:linha"})," for mesmo necess\xe1rio, cite junto o nome do que est\xe1 l\xe1 \u2014 assim quem ler\ndaqui a um m\xeas acha por ",(0,n.jsx)(o.code,{children:"grep"})," mesmo com o ponteiro deslocado."]})]})}function h(e={}){const{wrapper:o}={...(0,d.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(t,{...e})}):t(e)}},8453(e,o,s){s.d(o,{R:()=>a,x:()=>i});var r=s(6540);const n={},d=r.createContext(n);function a(e){const o=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function i(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),r.createElement(d.Provider,{value:o},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/assets/js/745ddde7.cbd3d5a7.js b/public/docs/assets/js/745ddde7.0263fb18.js
similarity index 99%
rename from public/docs/assets/js/745ddde7.cbd3d5a7.js
rename to public/docs/assets/js/745ddde7.0263fb18.js
index 619ec88a0..980e0dc46 100644
--- a/public/docs/assets/js/745ddde7.cbd3d5a7.js
+++ b/public/docs/assets/js/745ddde7.0263fb18.js
@@ -1 +1 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[889],{9541(e,s,o){o.r(s),o.d(s,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"stack","title":"Stack e ferramentas","description":"Three.js/WebGL sem build, Astro na Vercel, Supabase, o pipeline de gera\xe7\xe3o de asset (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform e as skills de agente \u2014 cada um com a vers\xe3o lida do package.json.","source":"@site/docs/stack.md","sourceDirName":".","slug":"/stack","permalink":"/docs/stack","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/stack.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"id":"stack","title":"Stack e ferramentas","sidebar_label":"Stack e ferramentas","sidebar_position":2,"description":"Three.js/WebGL sem build, Astro na Vercel, Supabase, o pipeline de gera\xe7\xe3o de asset (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform e as skills de agente \u2014 cada um com a vers\xe3o lida do package.json."},"sidebar":"dev","previous":{"title":"Come\xe7ando","permalink":"/docs/"},"next":{"title":"Instrumenta\xe7\xe3o de IA","permalink":"/docs/instrumentacao-ai"}}');var n=o(4848),d=o(8453);const a={id:"stack",title:"Stack e ferramentas",sidebar_label:"Stack e ferramentas",sidebar_position:2,description:"Three.js/WebGL sem build, Astro na Vercel, Supabase, o pipeline de gera\xe7\xe3o de asset (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform e as skills de agente \u2014 cada um com a vers\xe3o lida do package.json."},i="Stack e ferramentas",c={},l=[{value:"As duas zonas, e por que a fronteira \xe9 dura",id:"as-duas-zonas-e-por-que-a-fronteira-\xe9-dura",level:2},{value:"public/ \u2014 o JOGO: Three.js, WebGL, zero build",id:"public--o-jogo-threejs-webgl-zero-build",level:3},{value:"src/ \u2014 o SITE: Astro com SSR na Vercel",id:"src--o-site-astro-com-ssr-na-vercel",level:3},{value:"Banco \u2014 Postgres gerenciado, RLS e telemetria",id:"banco--postgres-gerenciado-rls-e-telemetria",level:3},{value:"Gera\xe7\xe3o de asset \u2014 o que \xe9 gerado por IA, e por qual servi\xe7o",id:"gera\xe7\xe3o-de-asset--o-que-\xe9-gerado-por-ia-e-por-qual-servi\xe7o",level:2},{value:"Personagens: mint.gg",id:"personagens-mintgg",level:3},{value:"Props 3D: Tripo3D e Meshy",id:"props-3d-tripo3d-e-meshy",level:3},{value:"Arte 2D: OpenRouter",id:"arte-2d-openrouter",level:3},{value:"As chaves",id:"as-chaves",level:3},{value:"Otimiza\xe7\xe3o de GLB: gltf-transform e meshoptimizer",id:"otimiza\xe7\xe3o-de-glb-gltf-transform-e-meshoptimizer",level:2},{value:"Playwright \u2014 todo arn\xeas que precisa de browser",id:"playwright--todo-arn\xeas-que-precisa-de-browser",level:2},{value:"Skills de agente",id:"skills-de-agente",level:2},{value:"O gauntlet loop",id:"o-gauntlet-loop",level:3},{value:"A documenta\xe7\xe3o",id:"a-documenta\xe7\xe3o",level:2}];function t(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"stack-e-ferramentas",children:"Stack e ferramentas"})}),"\n",(0,n.jsxs)(s.p,{children:["Esta p\xe1gina responde \xe0 pergunta ",(0,n.jsx)(s.em,{children:'"com o que isso \xe9 feito?"'})," \u2014 e responde com a ",(0,n.jsx)(s.strong,{children:"vers\xe3o\ndeclarada"}),", n\xe3o com a lembrada. A tabela abaixo \xe9 gerada por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),"\na partir do ",(0,n.jsx)(s.code,{children:"package.json"}),", do ",(0,n.jsx)(s.code,{children:"docs/package.json"})," e do pr\xf3prio Three.js vendorizado."]}),"\n","\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Camada"}),(0,n.jsx)(s.th,{children:"Ferramenta"}),(0,n.jsx)(s.th,{children:"Vers\xe3o"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Motor 3D (WebGL)"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Three.js"}),", vendorizado"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"r160"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Jogo"}),(0,n.jsxs)(s.td,{children:["ES modules vanilla, ",(0,n.jsx)(s.strong,{children:"zero build"})]}),(0,n.jsx)(s.td,{children:"44 arquivos"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Site"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Astro"})," com SSR"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^7.1.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Hospedagem"}),(0,n.jsxs)(s.td,{children:["adapter ",(0,n.jsx)(s.strong,{children:"Vercel"})]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^11.0.3"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Banco"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Postgres gerenciado"})," (RLS; schema privado, fora do repo)"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^2.110.7"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Browser nas r\xe9guas"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Playwright"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^1.62.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Pipeline de GLB"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"gltf-transform"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^4.4.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Compress\xe3o de malha"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"meshoptimizer"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^1.2.0"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Imagem (build e API)"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"sharp"})," \xb7 ",(0,n.jsx)(s.strong,{children:"resvg"})]}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"^0.35.3"})," \xb7 ",(0,n.jsx)(s.code,{children:"^2.6.2"})]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Esta documenta\xe7\xe3o"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Docusaurus"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"3.6.3"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Runtime de CI"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Node"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"22"})})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["Three.js sai de ",(0,n.jsx)(s.code,{children:"public/vendor/three.module.js"})," (",(0,n.jsx)(s.strong,{children:"sem CDN, sem npm no runtime"}),"). Astro e Vercel de ",(0,n.jsx)(s.code,{children:"package.json"})," + ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," + ",(0,n.jsx)(s.code,{children:"vercel.json"}),". Dos scripts de ",(0,n.jsx)(s.code,{children:"tools/"}),", ",(0,n.jsx)(s.strong,{children:"109"})," importam Playwright, ",(0,n.jsx)(s.strong,{children:"37"})," importam gltf-transform e ",(0,n.jsx)(s.strong,{children:"4"})," importam meshoptimizer."]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Bloco gerado por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(s.code,{children:"dependencies/devDependencies do package.json \xb7 REVISION de public/vendor/three.module.js"})]}),"\n"]}),"\n","\n",(0,n.jsx)(s.h2,{id:"as-duas-zonas-e-por-que-a-fronteira-\xe9-dura",children:"As duas zonas, e por que a fronteira \xe9 dura"}),"\n",(0,n.jsxs)(s.p,{children:["O reposit\xf3rio tem ",(0,n.jsx)(s.strong,{children:"duas aplica\xe7\xf5es com regras opostas"}),", e quase todo mal-entendido de\nquem chega nasce de trat\xe1-las como uma s\xf3."]}),"\n",(0,n.jsxs)(s.h3,{id:"public--o-jogo-threejs-webgl-zero-build",children:[(0,n.jsx)(s.code,{children:"public/"})," \u2014 o JOGO: Three.js, WebGL, zero build"]}),"\n",(0,n.jsxs)(s.p,{children:["O jogo \xe9 ",(0,n.jsx)(s.strong,{children:"JavaScript vanilla com ES modules servidos crus"}),". N\xe3o h\xe1 bundler, n\xe3o h\xe1\ntranspiler, n\xe3o h\xe1 passo de build. O browser baixa ",(0,n.jsx)(s.code,{children:"public/js/game.js"})," como est\xe1 no\nreposit\xf3rio."]}),"\n",(0,n.jsxs)(s.p,{children:["Isso \xe9 ",(0,n.jsx)(s.strong,{children:"decis\xe3o de projeto, n\xe3o pregui\xe7a"}),", e ela paga em tr\xeas lugares:"]}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"O jogo roda arrastando a pasta pra qualquer host est\xe1tico."})," N\xe3o depende do Astro,\nn\xe3o depende da Vercel, n\xe3o depende de npm no runtime. \xc9 o que torna vi\xe1vel entregar em\nportal (CrazyGames, itch) sem reescrever nada."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:["O arn\xeas consegue subir a classe ",(0,n.jsx)(s.code,{children:"Game"})," em node puro."]})," ",(0,n.jsx)(s.code,{children:"tools/eval/harness.mjs"}),"\nimporta o ",(0,n.jsx)(s.strong,{children:"c\xf3digo de produ\xe7\xe3o"})," com DOM e canvas stubados, e mede o jogo real em\nsegundos. Um bundler no meio quebraria isso \u2014 e sem isso n\xe3o existe quality gate."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:[(0,n.jsx)(s.code,{children:"node --check"})," em cada arquivo \xe9 um teste de sintaxe completo"]})," (",(0,n.jsx)(s.code,{children:"npm run syntax"}),"),\nporque o arquivo que o node parseia \xe9 byte a byte o que o browser executa."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["O pre\xe7o, que tamb\xe9m \xe9 real: ",(0,n.jsx)(s.strong,{children:"cache"}),". Sem build n\xe3o h\xe1 hash no nome do arquivo, ent\xe3o a\ninvalida\xe7\xe3o \xe9 manual \u2014 o ",(0,n.jsx)(s.code,{children:"?v="})," do import map. A regra e o que ela j\xe1 custou est\xe3o em\n",(0,n.jsx)(s.a,{href:"/docs/#as-duas-zonas",children:"Come\xe7ando"}),", num lugar s\xf3."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Three.js \xe9 vendorizado"})," em ",(0,n.jsx)(s.code,{children:"public/vendor/three.module.js"})," (mais ",(0,n.jsx)(s.code,{children:"vendor/addons/"}),").\nSem CDN e sem depend\xeancia de runtime: o import map aponta para o arquivo local. N\xe3o\nadicione CDN nem pacote de runtime sem abrir issue."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"WebGL \xe9 o alvo, e m\xe1quina fraca \xe9 requisito."})," Existe caminho ",(0,n.jsx)(s.code,{children:"quality: 'low'"})," sem\np\xf3s-processamento e kill-switch por querystring em toda mudan\xe7a arriscada (",(0,n.jsx)(s.code,{children:"?bloom=0"}),",\n",(0,n.jsx)(s.code,{children:"?ao=0"}),", ",(0,n.jsx)(s.code,{children:"?fxaa=0"}),", ",(0,n.jsx)(s.code,{children:"?water=0"}),"). Toda mudan\xe7a de gr\xe1fico que exija render extra tem que\ndeclarar o custo medido."]}),"\n",(0,n.jsxs)(s.h3,{id:"src--o-site-astro-com-ssr-na-vercel",children:[(0,n.jsx)(s.code,{children:"src/"})," \u2014 o SITE: Astro com SSR na Vercel"]}),"\n",(0,n.jsxs)(s.p,{children:["O site \xe9 ",(0,n.jsx)(s.a,{href:"https://astro.build",children:"Astro"})," com o adapter da Vercel. ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," est\xe1 em\n",(0,n.jsx)(s.code,{children:"output: 'static'"})," ",(0,n.jsx)(s.strong,{children:"com adapter"}),", e as rotas que precisam de servidor optam por\n",(0,n.jsx)(s.code,{children:"export const prerender = false"})," uma a uma \u2014 \xe9 o caso de ",(0,n.jsx)(s.code,{children:"/ranking"}),", ",(0,n.jsx)(s.code,{children:"/u/*"}),",\n",(0,n.jsx)(s.code,{children:"/sitemap.xml"})," e de todas as rotas ",(0,n.jsx)(s.code,{children:"/api/*"}),"."]}),"\n",(0,n.jsx)(s.p,{children:"Aqui framework \xe9 bem-vindo. As regras que valem:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["a ",(0,n.jsx)(s.code,{children:"service_role"})," do Supabase vive ",(0,n.jsx)(s.strong,{children:"s\xf3 no servidor"})," e nunca chega ao browser;"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"site"})," no ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," est\xe1 ",(0,n.jsxs)(s.strong,{children:["com ",(0,n.jsx)(s.code,{children:"www"})]}),", e todo canonical sai da\xed;"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"vercel.json"})," carrega os headers de seguran\xe7a (CSP, HSTS, nosniff, Referrer-Policy,\nPermissions-Policy) e o cache de CDN."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["E a pegadinha que custa a primeira hora de todo mundo: ",(0,n.jsxs)(s.strong,{children:[(0,n.jsx)(s.code,{children:"src/pages/index.astro"})," \xc9 o\njogo"]}),", servido na rota ",(0,n.jsx)(s.code,{children:"/"}),". N\xe3o existe ",(0,n.jsx)(s.code,{children:"public/index.html"}),"."]}),"\n",(0,n.jsx)(s.h3,{id:"banco--postgres-gerenciado-rls-e-telemetria",children:"Banco \u2014 Postgres gerenciado, RLS e telemetria"}),"\n",(0,n.jsxs)(s.p,{children:["O ranking e a telemetria vivem num Postgres gerenciado. Schema e migrations s\xe3o\nprivados (fora do repo \u2014 decis\xe3o de seguran\xe7a); o runtime s\xf3 usa as envs.\nofusca\xe7\xe3o opcional que foi entregue pronta e ",(0,n.jsx)(s.strong,{children:"deliberadamente n\xe3o aplicada"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["A seguran\xe7a n\xe3o vem de esconder a ",(0,n.jsx)(s.code,{children:"anon"})," key \u2014 ela \xe9 p\xfablica por design. Vem das\n",(0,n.jsx)(s.em,{children:"policies"}),", dos grants por coluna e do rate limit contado no Postgres\n(",(0,n.jsx)(s.code,{children:"src/lib/ratelimit.ts"})," + RPC ",(0,n.jsx)(s.code,{children:"rl_take"}),"), n\xe3o em mem\xf3ria de lambda."]}),"\n",(0,n.jsxs)(s.p,{children:["Identidade de jogador usa UID est\xe1vel para selecionar a conta e token para\nautenticar a sess\xe3o; nick \xe9 atributo de exibi\xe7\xe3o. Clientes e bancos antigos t\xeam\nfallback tempor\xe1rio por ",(0,n.jsx)(s.code,{children:"nick + token"}),", documentado em ",(0,n.jsx)(s.code,{children:"docs/seguranca.md"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["Hoje a ",(0,n.jsx)(s.code,{children:"anon"})," key ",(0,n.jsx)(s.strong,{children:"n\xe3o sai do servidor"}),": existia um ",(0,n.jsx)(s.code,{children:"GET /api/config"}),' que a entregava\nao browser "pro client ligar OAuth/storage", mas nenhum cliente chegou a usar, e a rota\nfoi removida (issue #41). Se OAuth entrar na mesa, ela volta \u2014 com rate limit.']}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"O ranking est\xe1 desligado hoje"})," (",(0,n.jsx)(s.code,{children:"RANKING_ON"})," em ",(0,n.jsx)(s.code,{children:"src/lib/site.ts"}),") e foi trocado por\ntelemetria an\xf4nima. \xc9 flag, n\xe3o remo\xe7\xe3o \u2014 detalhes em ",(0,n.jsx)(s.a,{href:"/docs/estado",children:"Estado atual"}),"."]}),"\n",(0,n.jsx)(s.admonition,{title:"Nada disso \xe9 obrigat\xf3rio pra rodar o jogo",type:"note",children:(0,n.jsxs)(s.p,{children:["Sem as vari\xe1veis do Supabase o site sobe igual: as rotas de ranking respondem\n",(0,n.jsx)(s.code,{children:"503 not_configured"})," e as p\xe1ginas mostram o aviso. O jogo em ",(0,n.jsx)(s.code,{children:"public/"})," ",(0,n.jsx)(s.strong,{children:"n\xe3o usa nenhuma\ndelas"}),". Ver ",(0,n.jsx)(s.code,{children:".env.example"}),"."]})}),"\n",(0,n.jsx)(s.h2,{id:"gera\xe7\xe3o-de-asset--o-que-\xe9-gerado-por-ia-e-por-qual-servi\xe7o",children:"Gera\xe7\xe3o de asset \u2014 o que \xe9 gerado por IA, e por qual servi\xe7o"}),"\n",(0,n.jsxs)(s.p,{children:["Quase todo asset 3D e 2D deste jogo \xe9 ",(0,n.jsx)(s.strong,{children:"gerado"}),", n\xe3o modelado \xe0 m\xe3o. O fluxo real, n\xe3o o\nhipot\xe9tico:"]}),"\n","\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Servi\xe7o"}),(0,n.jsx)(s.th,{children:"O que gera"}),(0,n.jsx)(s.th,{children:"Script"}),(0,n.jsx)(s.th,{children:"Chave"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"mint.gg"})," (Mint MCP)"]}),(0,n.jsx)(s.td,{children:"personagens rigados, packs, anima\xe7\xe3o"}),(0,n.jsxs)(s.td,{children:["ferramentas MCP; o registro do que foi gerado \xe9 ",(0,n.jsx)(s.code,{children:"mint-assets.json"})]}),(0,n.jsx)(s.td,{children:"conta do dono, via MCP"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tripo3D"})}),(0,n.jsx)(s.td,{children:"props 3D por texto (padr\xe3o)"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs --provider tripo"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"TRIPO_API_KEY"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Meshy"})}),(0,n.jsx)(s.td,{children:"props 3D por texto (alternativa) e rig"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs --provider meshy"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"MESHY_API_KEY"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"OpenRouter"})}),(0,n.jsx)(s.td,{children:"arte 2D (cartaz de fac\xe7\xe3o, wallpaper, splash)"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-image.mjs"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"OPENROUTER_API_KEY"})})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"mint-assets.json"})," registra ",(0,n.jsx)(s.strong,{children:"7 assets"})," gerados via Mint (3 ",(0,n.jsx)(s.code,{children:"mint-model"})," \xb7 4 ",(0,n.jsx)(s.code,{children:"mint-asset-pack"}),"), cada um com ",(0,n.jsx)(s.code,{children:"assetId"}),", ",(0,n.jsx)(s.code,{children:"chatUrl"})," e notas do que deu errado na tentativa anterior."]}),"\n",(0,n.jsxs)(s.p,{children:["As tr\xeas chaves de API vivem em ",(0,n.jsx)(s.code,{children:".env"})," na raiz \u2014 ",(0,n.jsxs)(s.strong,{children:["gitignored, modo 600, nunca em ",(0,n.jsx)(s.code,{children:"argv"})]})," (argv vaza no ",(0,n.jsx)(s.code,{children:"ps"})," de qualquer processo da m\xe1quina). Sem elas o jogo roda igual: o pipeline de gera\xe7\xe3o \xe9 offline, o resultado \xe9 que entra no reposit\xf3rio."]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Bloco gerado por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(s.code,{children:"git grep -l SDK -- tools/ | grep .mjs \xb7 mint-assets.json"})]}),"\n"]}),"\n","\n",(0,n.jsx)(s.h3,{id:"personagens-mintgg",children:"Personagens: mint.gg"}),"\n",(0,n.jsxs)(s.p,{children:["Os personagens jog\xe1veis s\xe3o GLB rigados gerados pelo ",(0,n.jsx)(s.strong,{children:"Mint"})," (mint.gg), pelas ferramentas\nMCP \u2014 ",(0,n.jsx)(s.code,{children:"start_model_generation"})," com ",(0,n.jsx)(s.code,{children:"riggable_character"})," em T-pose e m\xe3os vazias, depois\n",(0,n.jsx)(s.code,{children:"animate_generated_model"})," para sair com esqueleto."]}),"\n",(0,n.jsx)(s.p,{children:"Dois fatos n\xe3o \xf3bvios que economizam dinheiro e rodada:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"O modelo base do Mint n\xe3o vem rigado."})," O esqueleto s\xf3 aparece no passo de anima\xe7\xe3o.\nO caminho barato para um personagem novo \xe9: gerar a base \u2192 rigar com ",(0,n.jsx)(s.strong,{children:"um"})," clipe \u2192\nusar o ",(0,n.jsx)(s.code,{children:"rigged_character_glb"})," dele \u2192 reaproveitar os clipes compartilhados."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Os rigs Meshy compartilham os mesmos nomes de osso"})," (",(0,n.jsx)(s.code,{children:"Hips"}),", ",(0,n.jsx)(s.code,{children:"Spine"}),", ",(0,n.jsx)(s.code,{children:"Head"}),",\n",(0,n.jsx)(s.code,{children:"RightHand"}),"\u2026), ent\xe3o um pack de clipes gerado uma vez casa por nome em qualquer rig da\nfam\xedlia. \xc9 por isso que ",(0,n.jsx)(s.code,{children:"public/models/anims/"})," tem clipe compartilhado e clipe pr\xf3prio\nao mesmo tempo, e por isso existe o manifesto ",(0,n.jsx)(s.code,{children:"index.json"})," (",(0,n.jsx)(s.code,{children:"npm run anims"}),") \u2014 sem ele o\njogo pedia clipe de quem n\xe3o tem e enchia o console de 404."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"mint-assets.json"})," \xe9 o registro do que foi gerado: ",(0,n.jsx)(s.code,{children:"assetId"}),", ",(0,n.jsx)(s.code,{children:"chatUrl"})," e uma nota do que\ndeu errado na tentativa anterior. ",(0,n.jsx)(s.strong,{children:"Sem esse registro n\xe3o d\xe1 para revisar nem regerar"})," \u2014\no asset vira um bin\xe1rio sem proced\xeancia no meio do reposit\xf3rio."]}),"\n",(0,n.jsx)(s.h3,{id:"props-3d-tripo3d-e-meshy",children:"Props 3D: Tripo3D e Meshy"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs"})," gera um prop por texto, baixa o GLB e grava ",(0,n.jsx)(s.strong,{children:"j\xe1 otimizado"})," em\n",(0,n.jsx)(s.code,{children:"public/models/props/"}),":"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:'node tools/gen-asset.mjs --prompt "caixa de som de baile" --id caixa_som\nnode tools/gen-asset.mjs --provider meshy --prompt "carro tunado" --id carro_tunado\nnode tools/gen-asset.mjs --resume public/ \u2014 o JOGO: Three.js, WebGL, zero build",id:"public--o-jogo-threejs-webgl-zero-build",level:3},{value:"src/ \u2014 o SITE: Astro com SSR na Vercel",id:"src--o-site-astro-com-ssr-na-vercel",level:3},{value:"Banco \u2014 Postgres gerenciado, RLS e telemetria",id:"banco--postgres-gerenciado-rls-e-telemetria",level:3},{value:"Gera\xe7\xe3o de asset \u2014 o que \xe9 gerado por IA, e por qual servi\xe7o",id:"gera\xe7\xe3o-de-asset--o-que-\xe9-gerado-por-ia-e-por-qual-servi\xe7o",level:2},{value:"Personagens: mint.gg",id:"personagens-mintgg",level:3},{value:"Props 3D: Tripo3D e Meshy",id:"props-3d-tripo3d-e-meshy",level:3},{value:"Arte 2D: OpenRouter",id:"arte-2d-openrouter",level:3},{value:"As chaves",id:"as-chaves",level:3},{value:"Otimiza\xe7\xe3o de GLB: gltf-transform e meshoptimizer",id:"otimiza\xe7\xe3o-de-glb-gltf-transform-e-meshoptimizer",level:2},{value:"Playwright \u2014 todo arn\xeas que precisa de browser",id:"playwright--todo-arn\xeas-que-precisa-de-browser",level:2},{value:"Skills de agente",id:"skills-de-agente",level:2},{value:"O gauntlet loop",id:"o-gauntlet-loop",level:3},{value:"A documenta\xe7\xe3o",id:"a-documenta\xe7\xe3o",level:2}];function t(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"stack-e-ferramentas",children:"Stack e ferramentas"})}),"\n",(0,n.jsxs)(s.p,{children:["Esta p\xe1gina responde \xe0 pergunta ",(0,n.jsx)(s.em,{children:'"com o que isso \xe9 feito?"'})," \u2014 e responde com a ",(0,n.jsx)(s.strong,{children:"vers\xe3o\ndeclarada"}),", n\xe3o com a lembrada. A tabela abaixo \xe9 gerada por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),"\na partir do ",(0,n.jsx)(s.code,{children:"package.json"}),", do ",(0,n.jsx)(s.code,{children:"docs/package.json"})," e do pr\xf3prio Three.js vendorizado."]}),"\n","\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Camada"}),(0,n.jsx)(s.th,{children:"Ferramenta"}),(0,n.jsx)(s.th,{children:"Vers\xe3o"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Motor 3D (WebGL)"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Three.js"}),", vendorizado"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"r160"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Jogo"}),(0,n.jsxs)(s.td,{children:["ES modules vanilla, ",(0,n.jsx)(s.strong,{children:"zero build"})]}),(0,n.jsx)(s.td,{children:"44 arquivos"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Site"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Astro"})," com SSR"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^7.1.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Hospedagem"}),(0,n.jsxs)(s.td,{children:["adapter ",(0,n.jsx)(s.strong,{children:"Vercel"})]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^11.0.6"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Banco"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Postgres gerenciado"})," (RLS; schema privado, fora do repo)"]}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^2.110.7"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Browser nas r\xe9guas"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Playwright"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^1.62.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Pipeline de GLB"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"gltf-transform"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^4.4.1"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Compress\xe3o de malha"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"meshoptimizer"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"^1.2.0"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Imagem (build e API)"}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"sharp"})," \xb7 ",(0,n.jsx)(s.strong,{children:"resvg"})]}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"^0.35.3"})," \xb7 ",(0,n.jsx)(s.code,{children:"^2.6.2"})]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Esta documenta\xe7\xe3o"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Docusaurus"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"3.6.3"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Runtime de CI"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Node"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"22"})})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["Three.js sai de ",(0,n.jsx)(s.code,{children:"public/vendor/three.module.js"})," (",(0,n.jsx)(s.strong,{children:"sem CDN, sem npm no runtime"}),"). Astro e Vercel de ",(0,n.jsx)(s.code,{children:"package.json"})," + ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," + ",(0,n.jsx)(s.code,{children:"vercel.json"}),". Dos scripts de ",(0,n.jsx)(s.code,{children:"tools/"}),", ",(0,n.jsx)(s.strong,{children:"110"})," importam Playwright, ",(0,n.jsx)(s.strong,{children:"37"})," importam gltf-transform e ",(0,n.jsx)(s.strong,{children:"4"})," importam meshoptimizer."]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Bloco gerado por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(s.code,{children:"dependencies/devDependencies do package.json \xb7 REVISION de public/vendor/three.module.js"})]}),"\n"]}),"\n","\n",(0,n.jsx)(s.h2,{id:"as-duas-zonas-e-por-que-a-fronteira-\xe9-dura",children:"As duas zonas, e por que a fronteira \xe9 dura"}),"\n",(0,n.jsxs)(s.p,{children:["O reposit\xf3rio tem ",(0,n.jsx)(s.strong,{children:"duas aplica\xe7\xf5es com regras opostas"}),", e quase todo mal-entendido de\nquem chega nasce de trat\xe1-las como uma s\xf3."]}),"\n",(0,n.jsxs)(s.h3,{id:"public--o-jogo-threejs-webgl-zero-build",children:[(0,n.jsx)(s.code,{children:"public/"})," \u2014 o JOGO: Three.js, WebGL, zero build"]}),"\n",(0,n.jsxs)(s.p,{children:["O jogo \xe9 ",(0,n.jsx)(s.strong,{children:"JavaScript vanilla com ES modules servidos crus"}),". N\xe3o h\xe1 bundler, n\xe3o h\xe1\ntranspiler, n\xe3o h\xe1 passo de build. O browser baixa ",(0,n.jsx)(s.code,{children:"public/js/game.js"})," como est\xe1 no\nreposit\xf3rio."]}),"\n",(0,n.jsxs)(s.p,{children:["Isso \xe9 ",(0,n.jsx)(s.strong,{children:"decis\xe3o de projeto, n\xe3o pregui\xe7a"}),", e ela paga em tr\xeas lugares:"]}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"O jogo roda arrastando a pasta pra qualquer host est\xe1tico."})," N\xe3o depende do Astro,\nn\xe3o depende da Vercel, n\xe3o depende de npm no runtime. \xc9 o que torna vi\xe1vel entregar em\nportal (CrazyGames, itch) sem reescrever nada."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:["O arn\xeas consegue subir a classe ",(0,n.jsx)(s.code,{children:"Game"})," em node puro."]})," ",(0,n.jsx)(s.code,{children:"tools/eval/harness.mjs"}),"\nimporta o ",(0,n.jsx)(s.strong,{children:"c\xf3digo de produ\xe7\xe3o"})," com DOM e canvas stubados, e mede o jogo real em\nsegundos. Um bundler no meio quebraria isso \u2014 e sem isso n\xe3o existe quality gate."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:[(0,n.jsx)(s.code,{children:"node --check"})," em cada arquivo \xe9 um teste de sintaxe completo"]})," (",(0,n.jsx)(s.code,{children:"npm run syntax"}),"),\nporque o arquivo que o node parseia \xe9 byte a byte o que o browser executa."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["O pre\xe7o, que tamb\xe9m \xe9 real: ",(0,n.jsx)(s.strong,{children:"cache"}),". Sem build n\xe3o h\xe1 hash no nome do arquivo, ent\xe3o a\ninvalida\xe7\xe3o \xe9 manual \u2014 o ",(0,n.jsx)(s.code,{children:"?v="})," do import map. A regra e o que ela j\xe1 custou est\xe3o em\n",(0,n.jsx)(s.a,{href:"/docs/#as-duas-zonas",children:"Come\xe7ando"}),", num lugar s\xf3."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Three.js \xe9 vendorizado"})," em ",(0,n.jsx)(s.code,{children:"public/vendor/three.module.js"})," (mais ",(0,n.jsx)(s.code,{children:"vendor/addons/"}),").\nSem CDN e sem depend\xeancia de runtime: o import map aponta para o arquivo local. N\xe3o\nadicione CDN nem pacote de runtime sem abrir issue."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"WebGL \xe9 o alvo, e m\xe1quina fraca \xe9 requisito."})," Existe caminho ",(0,n.jsx)(s.code,{children:"quality: 'low'"})," sem\np\xf3s-processamento e kill-switch por querystring em toda mudan\xe7a arriscada (",(0,n.jsx)(s.code,{children:"?bloom=0"}),",\n",(0,n.jsx)(s.code,{children:"?ao=0"}),", ",(0,n.jsx)(s.code,{children:"?fxaa=0"}),", ",(0,n.jsx)(s.code,{children:"?water=0"}),"). Toda mudan\xe7a de gr\xe1fico que exija render extra tem que\ndeclarar o custo medido."]}),"\n",(0,n.jsxs)(s.h3,{id:"src--o-site-astro-com-ssr-na-vercel",children:[(0,n.jsx)(s.code,{children:"src/"})," \u2014 o SITE: Astro com SSR na Vercel"]}),"\n",(0,n.jsxs)(s.p,{children:["O site \xe9 ",(0,n.jsx)(s.a,{href:"https://astro.build",children:"Astro"})," com o adapter da Vercel. ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," est\xe1 em\n",(0,n.jsx)(s.code,{children:"output: 'static'"})," ",(0,n.jsx)(s.strong,{children:"com adapter"}),", e as rotas que precisam de servidor optam por\n",(0,n.jsx)(s.code,{children:"export const prerender = false"})," uma a uma \u2014 \xe9 o caso de ",(0,n.jsx)(s.code,{children:"/ranking"}),", ",(0,n.jsx)(s.code,{children:"/u/*"}),",\n",(0,n.jsx)(s.code,{children:"/sitemap.xml"})," e de todas as rotas ",(0,n.jsx)(s.code,{children:"/api/*"}),"."]}),"\n",(0,n.jsx)(s.p,{children:"Aqui framework \xe9 bem-vindo. As regras que valem:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["a ",(0,n.jsx)(s.code,{children:"service_role"})," do Supabase vive ",(0,n.jsx)(s.strong,{children:"s\xf3 no servidor"})," e nunca chega ao browser;"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"site"})," no ",(0,n.jsx)(s.code,{children:"astro.config.mjs"})," est\xe1 ",(0,n.jsxs)(s.strong,{children:["com ",(0,n.jsx)(s.code,{children:"www"})]}),", e todo canonical sai da\xed;"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"vercel.json"})," carrega os headers de seguran\xe7a (CSP, HSTS, nosniff, Referrer-Policy,\nPermissions-Policy) e o cache de CDN."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["E a pegadinha que custa a primeira hora de todo mundo: ",(0,n.jsxs)(s.strong,{children:[(0,n.jsx)(s.code,{children:"src/pages/index.astro"})," \xc9 o\njogo"]}),", servido na rota ",(0,n.jsx)(s.code,{children:"/"}),". N\xe3o existe ",(0,n.jsx)(s.code,{children:"public/index.html"}),"."]}),"\n",(0,n.jsx)(s.h3,{id:"banco--postgres-gerenciado-rls-e-telemetria",children:"Banco \u2014 Postgres gerenciado, RLS e telemetria"}),"\n",(0,n.jsxs)(s.p,{children:["O ranking e a telemetria vivem num Postgres gerenciado. Schema e migrations s\xe3o\nprivados (fora do repo \u2014 decis\xe3o de seguran\xe7a); o runtime s\xf3 usa as envs.\nofusca\xe7\xe3o opcional que foi entregue pronta e ",(0,n.jsx)(s.strong,{children:"deliberadamente n\xe3o aplicada"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["A seguran\xe7a n\xe3o vem de esconder a ",(0,n.jsx)(s.code,{children:"anon"})," key \u2014 ela \xe9 p\xfablica por design. Vem das\n",(0,n.jsx)(s.em,{children:"policies"}),", dos grants por coluna e do rate limit contado no Postgres\n(",(0,n.jsx)(s.code,{children:"src/lib/ratelimit.ts"})," + RPC ",(0,n.jsx)(s.code,{children:"rl_take"}),"), n\xe3o em mem\xf3ria de lambda."]}),"\n",(0,n.jsxs)(s.p,{children:["Identidade de jogador usa UID est\xe1vel para selecionar a conta e token para\nautenticar a sess\xe3o; nick \xe9 atributo de exibi\xe7\xe3o. Clientes e bancos antigos t\xeam\nfallback tempor\xe1rio por ",(0,n.jsx)(s.code,{children:"nick + token"}),", documentado em ",(0,n.jsx)(s.code,{children:"docs/seguranca.md"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["Hoje a ",(0,n.jsx)(s.code,{children:"anon"})," key ",(0,n.jsx)(s.strong,{children:"n\xe3o sai do servidor"}),": existia um ",(0,n.jsx)(s.code,{children:"GET /api/config"}),' que a entregava\nao browser "pro client ligar OAuth/storage", mas nenhum cliente chegou a usar, e a rota\nfoi removida (issue #41). Se OAuth entrar na mesa, ela volta \u2014 com rate limit.']}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"O ranking est\xe1 desligado hoje"})," (",(0,n.jsx)(s.code,{children:"RANKING_ON"})," em ",(0,n.jsx)(s.code,{children:"src/lib/site.ts"}),") e foi trocado por\ntelemetria an\xf4nima. \xc9 flag, n\xe3o remo\xe7\xe3o \u2014 detalhes em ",(0,n.jsx)(s.a,{href:"/docs/estado",children:"Estado atual"}),"."]}),"\n",(0,n.jsx)(s.admonition,{title:"Nada disso \xe9 obrigat\xf3rio pra rodar o jogo",type:"note",children:(0,n.jsxs)(s.p,{children:["Sem as vari\xe1veis do Supabase o site sobe igual: as rotas de ranking respondem\n",(0,n.jsx)(s.code,{children:"503 not_configured"})," e as p\xe1ginas mostram o aviso. O jogo em ",(0,n.jsx)(s.code,{children:"public/"})," ",(0,n.jsx)(s.strong,{children:"n\xe3o usa nenhuma\ndelas"}),". Ver ",(0,n.jsx)(s.code,{children:".env.example"}),"."]})}),"\n",(0,n.jsx)(s.h2,{id:"gera\xe7\xe3o-de-asset--o-que-\xe9-gerado-por-ia-e-por-qual-servi\xe7o",children:"Gera\xe7\xe3o de asset \u2014 o que \xe9 gerado por IA, e por qual servi\xe7o"}),"\n",(0,n.jsxs)(s.p,{children:["Quase todo asset 3D e 2D deste jogo \xe9 ",(0,n.jsx)(s.strong,{children:"gerado"}),", n\xe3o modelado \xe0 m\xe3o. O fluxo real, n\xe3o o\nhipot\xe9tico:"]}),"\n","\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Servi\xe7o"}),(0,n.jsx)(s.th,{children:"O que gera"}),(0,n.jsx)(s.th,{children:"Script"}),(0,n.jsx)(s.th,{children:"Chave"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"mint.gg"})," (Mint MCP)"]}),(0,n.jsx)(s.td,{children:"personagens rigados, packs, anima\xe7\xe3o"}),(0,n.jsxs)(s.td,{children:["ferramentas MCP; o registro do que foi gerado \xe9 ",(0,n.jsx)(s.code,{children:"mint-assets.json"})]}),(0,n.jsx)(s.td,{children:"conta do dono, via MCP"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tripo3D"})}),(0,n.jsx)(s.td,{children:"props 3D por texto (padr\xe3o)"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs --provider tripo"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"TRIPO_API_KEY"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Meshy"})}),(0,n.jsx)(s.td,{children:"props 3D por texto (alternativa) e rig"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs --provider meshy"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"MESHY_API_KEY"})})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"OpenRouter"})}),(0,n.jsx)(s.td,{children:"arte 2D (cartaz de fac\xe7\xe3o, wallpaper, splash)"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"tools/gen-image.mjs"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"OPENROUTER_API_KEY"})})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"mint-assets.json"})," registra ",(0,n.jsx)(s.strong,{children:"7 assets"})," gerados via Mint (3 ",(0,n.jsx)(s.code,{children:"mint-model"})," \xb7 4 ",(0,n.jsx)(s.code,{children:"mint-asset-pack"}),"), cada um com ",(0,n.jsx)(s.code,{children:"assetId"}),", ",(0,n.jsx)(s.code,{children:"chatUrl"})," e notas do que deu errado na tentativa anterior."]}),"\n",(0,n.jsxs)(s.p,{children:["As tr\xeas chaves de API vivem em ",(0,n.jsx)(s.code,{children:".env"})," na raiz \u2014 ",(0,n.jsxs)(s.strong,{children:["gitignored, modo 600, nunca em ",(0,n.jsx)(s.code,{children:"argv"})]})," (argv vaza no ",(0,n.jsx)(s.code,{children:"ps"})," de qualquer processo da m\xe1quina). Sem elas o jogo roda igual: o pipeline de gera\xe7\xe3o \xe9 offline, o resultado \xe9 que entra no reposit\xf3rio."]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Bloco gerado por ",(0,n.jsx)(s.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,n.jsx)(s.code,{children:"git grep -l SDK -- tools/ | grep .mjs \xb7 mint-assets.json"})]}),"\n"]}),"\n","\n",(0,n.jsx)(s.h3,{id:"personagens-mintgg",children:"Personagens: mint.gg"}),"\n",(0,n.jsxs)(s.p,{children:["Os personagens jog\xe1veis s\xe3o GLB rigados gerados pelo ",(0,n.jsx)(s.strong,{children:"Mint"})," (mint.gg), pelas ferramentas\nMCP \u2014 ",(0,n.jsx)(s.code,{children:"start_model_generation"})," com ",(0,n.jsx)(s.code,{children:"riggable_character"})," em T-pose e m\xe3os vazias, depois\n",(0,n.jsx)(s.code,{children:"animate_generated_model"})," para sair com esqueleto."]}),"\n",(0,n.jsx)(s.p,{children:"Dois fatos n\xe3o \xf3bvios que economizam dinheiro e rodada:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"O modelo base do Mint n\xe3o vem rigado."})," O esqueleto s\xf3 aparece no passo de anima\xe7\xe3o.\nO caminho barato para um personagem novo \xe9: gerar a base \u2192 rigar com ",(0,n.jsx)(s.strong,{children:"um"})," clipe \u2192\nusar o ",(0,n.jsx)(s.code,{children:"rigged_character_glb"})," dele \u2192 reaproveitar os clipes compartilhados."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Os rigs Meshy compartilham os mesmos nomes de osso"})," (",(0,n.jsx)(s.code,{children:"Hips"}),", ",(0,n.jsx)(s.code,{children:"Spine"}),", ",(0,n.jsx)(s.code,{children:"Head"}),",\n",(0,n.jsx)(s.code,{children:"RightHand"}),"\u2026), ent\xe3o um pack de clipes gerado uma vez casa por nome em qualquer rig da\nfam\xedlia. \xc9 por isso que ",(0,n.jsx)(s.code,{children:"public/models/anims/"})," tem clipe compartilhado e clipe pr\xf3prio\nao mesmo tempo, e por isso existe o manifesto ",(0,n.jsx)(s.code,{children:"index.json"})," (",(0,n.jsx)(s.code,{children:"npm run anims"}),") \u2014 sem ele o\njogo pedia clipe de quem n\xe3o tem e enchia o console de 404."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"mint-assets.json"})," \xe9 o registro do que foi gerado: ",(0,n.jsx)(s.code,{children:"assetId"}),", ",(0,n.jsx)(s.code,{children:"chatUrl"})," e uma nota do que\ndeu errado na tentativa anterior. ",(0,n.jsx)(s.strong,{children:"Sem esse registro n\xe3o d\xe1 para revisar nem regerar"})," \u2014\no asset vira um bin\xe1rio sem proced\xeancia no meio do reposit\xf3rio."]}),"\n",(0,n.jsx)(s.h3,{id:"props-3d-tripo3d-e-meshy",children:"Props 3D: Tripo3D e Meshy"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"tools/gen-asset.mjs"})," gera um prop por texto, baixa o GLB e grava ",(0,n.jsx)(s.strong,{children:"j\xe1 otimizado"})," em\n",(0,n.jsx)(s.code,{children:"public/models/props/"}),":"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:'node tools/gen-asset.mjs --prompt "caixa de som de baile" --id caixa_som\nnode tools/gen-asset.mjs --provider meshy --prompt "carro tunado" --id carro_tunado\nnode tools/gen-asset.mjs --resume ui-check.mjs",id:"muta\xe7\xe3o-como-coisa-de-primeira-classe-ui-checkmjs",level:3},{value:"Como escrever uma invariante",id:"como-escrever-uma-invariante",level:2},{value:"Anti-padr\xf5es que j\xe1 custaram caro aqui",id:"anti-padr\xf5es-que-j\xe1-custaram-caro-aqui",level:3},{value:"Esta p\xe1gina \xe9 a doutrina. O passo a passo \xe9 uma skill",id:"esta-p\xe1gina-\xe9-a-doutrina-o-passo-a-passo-\xe9-uma-skill",level:2},{value:"Port\xf5es que N\xc3O cabem no check, e por qu\xea",id:"port\xf5es-que-n\xe3o-cabem-no-check-e-por-qu\xea",level:2},{value:"Rodar o quality gate",id:"rodar-o-quality-gate",level:2}];function l(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,n.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.header,{children:(0,s.jsx)(o.h1,{id:"o-quality-gate-invariantes-proced\xeancia-e-muta\xe7\xe3o",children:"O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o"})}),"\n",(0,s.jsxs)(o.p,{children:["O quality gate deste reposit\xf3rio \xe9 um arquivo: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),". Ele roda em node puro\ne sai com c\xf3digo 1 se qualquer invariante ",(0,s.jsx)(o.strong,{children:"cr\xedtica"})," falhar. \xc9 o que o CI executa em todo\nPR (",(0,s.jsx)(o.code,{children:".github/workflows/ci.yml"}),")."]}),"\n","\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),": ",(0,s.jsx)(o.strong,{children:"2.275 linhas"}),", ",(0,s.jsx)(o.strong,{children:"65 identificadores de invariante declarados"})," (",(0,s.jsx)(o.code,{children:"put()"}),"), dos quais ",(0,s.jsx)(o.strong,{children:"28"})," t\xeam caminho de ",(0,s.jsx)(o.code,{children:"skip()"})," declarado."]}),"\n",(0,s.jsxs)(o.li,{children:["O arn\xeas inteiro s\xe3o ",(0,s.jsx)(o.strong,{children:"192 scripts"})," em ",(0,s.jsx)(o.code,{children:"tools/eval/"})," (",(0,s.jsx)(o.code,{children:".mjs"})," + ",(0,s.jsx)(o.code,{children:".py"}),"), mais ",(0,s.jsx)(o.strong,{children:"54 scripts"})," de pipeline em ",(0,s.jsx)(o.code,{children:"tools/"}),"."]}),"\n",(0,s.jsxs)(o.li,{children:["Quantas invariantes rodam como ",(0,s.jsx)(o.strong,{children:"cr\xedticas"})," numa execu\xe7\xe3o ",(0,s.jsx)(o.strong,{children:"n\xe3o \xe9 deriv\xe1vel do fonte"}),": depende de qual insumo existe na m\xe1quina (o JSON do auditor de viewmodel, um GLB, uma pasta de anims). Esse n\xfamero s\xf3 sai rodando o quality gate \u2014 e o lugar dele \xe9 o cabe\xe7alho do ",(0,s.jsx)(o.code,{children:"KNOWN-BUGS.md"}),", atualizado com sa\xedda real."]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Reproduza:"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\ngrep -o \"skip('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\n"})}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Bloco gerado por ",(0,s.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,s.jsx)(o.code,{children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l"})]}),"\n"]}),"\n","\n",(0,s.jsxs)(o.p,{children:["O terceiro item acima \xe9 a distin\xe7\xe3o que mais confunde quem chega: ",(0,s.jsx)(o.strong,{children:"identificador\ndeclarado \u2260 invariante avaliada."})," V\xe1rias viram ",(0,s.jsx)(o.code,{children:"skip"})," em vez de ",(0,s.jsx)(o.code,{children:"put"})," quando falta o\ninsumo delas (o JSON do auditor de viewmodel, um GLB, uma pasta de anims). ",(0,s.jsx)(o.code,{children:"skip"})," \xe9\n",(0,s.jsx)(o.strong,{children:"quality gate verde por aus\xeancia de dado"}),', e \xe9 por isso que ele sempre carrega o motivo. Ver\n"Severidade", abaixo.']}),"\n",(0,s.jsx)(o.p,{children:"Esta p\xe1gina \xe9 a mais \xfatil do site. Se voc\xea s\xf3 for ler uma, leia esta."}),"\n",(0,s.jsx)(o.h2,{id:"por-que-ele-existe",children:"Por que ele existe"}),"\n",(0,s.jsxs)(o.p,{children:["Do cabe\xe7alho do pr\xf3prio arquivo, ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:5-19"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["O dono passou 3 dias num ciclo em que cada rodada consertava uma coisa e quebrava\noutra, e a gente s\xf3 descobria uma rodada depois. A causa n\xe3o era falta de cuidado:\nera falta de R\xc9GUA. Um cr\xedtico (humano ou agente) julga screenshot; consist\xeancia e\nflow s\xe3o propriedades do jogo ",(0,s.jsx)(o.strong,{children:"EM MOVIMENTO"}),", e quase todo defeito que ele reportou\nn\xe3o \xe9 gosto \u2014 \xe9 invariante violada."]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"E a tradu\xe7\xe3o, que \xe9 a coisa mais importante deste reposit\xf3rio inteiro:"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"O que o dono disse"}),(0,s.jsx)(o.th,{children:"Qual invariante isso virou"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"as m\xe3os est\xe3o soltas no ar"'}),(0,s.jsx)(o.td,{children:"dist\xe2ncia m\xe3o\u2194grip tem um teto"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"a arma aponta pra baixo"'}),(0,s.jsx)(o.td,{children:"o cano tem um \xe2ngulo m\xe1ximo"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"no ADS n\xe3o vejo a arma nem a mira"'}),(0,s.jsx)(o.td,{children:"a arma tem \xe1rea m\xednima e m\xe1xima"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"sniper sem zoom"'}),(0,s.jsx)(o.td,{children:"FOV mirando < FOV de quadril"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"v\xe1rias armas com visual igual"'}),(0,s.jsx)(o.td,{children:"silhuetas t\xeam que diferir"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"o bot atira do nada"'}),(0,s.jsx)(o.td,{children:"dano exige LOS anterior"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"tem 2 me eliminando"'}),(0,s.jsx)(o.td,{children:"1 killfeed por morte"})]})]})]}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:20-21"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"REGRA DE OURO: nada \xe9 commitado com invariante VERMELHA. E todo bug novo que o dono\nreportar vira uma invariante aqui \u2014 \xe9 assim que ele nunca volta."})}),"\n"]}),"\n",(0,s.jsx)(o.h2,{id:"o-que-\xe9-uma-invariante-aqui",children:"O que \xe9 uma invariante aqui"}),"\n",(0,s.jsxs)(o.p,{children:["Uma invariante \xe9 uma ",(0,s.jsx)(o.strong,{children:"propriedade do jogo que d\xe1 pra medir sem um humano olhando"}),", com\num teto ou uma faixa que tem proced\xeancia. N\xe3o \xe9 teste unit\xe1rio: quase nenhuma invariante\ntesta uma fun\xe7\xe3o. Elas medem o ",(0,s.jsx)(o.strong,{children:"estado do jogo rodando de verdade"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"Tr\xeas formas, todas presentes no arquivo:"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"1. Lida do c\xf3digo-fonte."})," Barata, roda em milissegundos, pega classes inteiras de bug.\nExemplo real, ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1439-1446"}),":"]}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-js",children:"// ARM1 \u2014 toda arma com luneta precisa de zoom de verdade. \"Snipers sem zoom\"\n// \xe9 reclama\xe7\xe3o literal; a solu\xe7\xe3o N\xc3O \xe9 tirar a luneta, \xe9 fazer a certa.\nconst bloco = gsrc.slice(0, gsrc.indexOf('};', gsrc.indexOf('const WEAPONS')) + 2);\nconst linhas = bloco.split('\\n').filter((l) => /^\\s*\\w+:\\s*\\{/.test(l));\nconst semZoom = linhas.filter((l) => /scope:\\s*true/.test(l) && !/spreadScope/.test(l))\n .map((l) => l.trim().split(':')[0]);\nput('ARM1', 'toda arma com scope:true declara spreadScope', semZoom.length === 0,\n semZoom.length ? semZoom.join(', ') : `${linhas.length} armas conferidas`);\n"})}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"2. Medida no jogo real rodando em node."})," ",(0,s.jsx)(o.code,{children:"tools/eval/harness.mjs"})," sobe a classe ",(0,s.jsx)(o.code,{children:"Game"}),"\nde verdade, com os mapas de verdade, com DOM/canvas stubado. \xc9 o ",(0,s.jsx)(o.strong,{children:"c\xf3digo de produ\xe7\xe3o"}),"\nque \xe9 medido, n\xe3o uma reimplementa\xe7\xe3o \u2014 ",(0,s.jsx)(o.code,{children:"tools/eval/botsim.mjs:8-9"}),": ",(0,s.jsx)(o.em,{children:'"se o n\xfamero\nmelhorar aqui, melhorou no jogo"'}),". Daqui saem BOT1\u2013BOT8, MAP1\u2013MAP3, CTF1, MAT1/MAT2,\nFOG1, TEX1, VM14, MOD1/MOD2."]}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"3. Medida na geometria dos assets."})," ",(0,s.jsx)(o.code,{children:"vm-mint-audit.mjs"})," abre todos os GLBs de arma com um\nparser de GLB pr\xf3prio e projeta o viewmodel na tela. Daqui saem VM1\u2013VM19."]}),"\n",(0,s.jsxs)(o.p,{children:["O que ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," cabe aqui: invariante que exige pixel de browser. Essas est\xe3o marcadas\n",(0,s.jsx)(o.code,{children:"browser"})," e s\xe3o puladas, com o motivo dito \u2014 SwiftShader custa ~4 min por carga de mapa\nnesta m\xe1quina (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:99"}),")."]}),"\n",(0,s.jsx)(o.h3,{id:"severidade",children:"Severidade"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"put(id, desc, ok, evid, sev)"})," aceita ",(0,s.jsx)(o.code,{children:"'crit'"})," (padr\xe3o) ou ",(0,s.jsx)(o.code,{children:"'warn'"}),"\n(",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:81-82"}),"). Cr\xedtica vermelha reprova o PR. Warn \xe9 ru\xeddo medido que\nalgu\xe9m precisa olhar mas n\xe3o bloqueia \u2014 \xe9 onde vivem BOT1/BOT2/BOT3/BOT6/BOT7, ARM4 e\nARM5. ",(0,s.jsx)(o.code,{children:"skip()"})," \xe9 o terceiro estado, e ele \xe9 ",(0,s.jsx)(o.strong,{children:"perigoso"}),": quality gate verde por aus\xeancia de\ndado. Por isso todo ",(0,s.jsx)(o.code,{children:"skip"})," carrega o motivo."]}),"\n",(0,s.jsx)(o.h2,{id:"as-duas-leis-da-casa",children:"As duas leis da casa"}),"\n",(0,s.jsx)(o.h3,{id:"lei-1--inten\xe7\xe3o-que-n\xe3o-vira-invariante-\xe9-otimizada-para-fora",children:"Lei 1 \u2014 Inten\xe7\xe3o que n\xe3o vira invariante \xe9 otimizada para fora"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:452-461"}),"."]})," O caso, literal:"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["a rodada anterior levou o quality gate de ",(0,s.jsx)(o.strong,{children:"16/21 para 19/21 sem afrouxar um teto sequer"})," e\nmesmo assim foi ",(0,s.jsx)(o.strong,{children:"REPROVADA"})," pelo dono, porque para fechar VM5/VM10 ela ",(0,s.jsxs)(o.strong,{children:["ZEROU o\n",(0,s.jsx)(o.code,{children:"VM_OFF"})," y"]}),' e mudou o look em sil\xeancio. Nenhuma invariante codificava "onde fica a\nboca do cano", ent\xe3o a m\xe9trica foi otimizada e a INTEN\xc7\xc3O foi destru\xedda. Lei de\nGoodhart, na \xedntegra. ',(0,s.jsx)(o.strong,{children:"INTEN\xc7\xc3O QUE N\xc3O VIRA INVARIANTE \xc9 OTIMIZADA PARA FORA."})]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Leia de novo o que aconteceu, porque \xe9 contraintuitivo: o agente ",(0,s.jsx)(o.strong,{children:"n\xe3o trapaceou"}),". Ele\nn\xe3o afrouxou nenhum teto. Ele subiu o placar de verdade. E o resultado foi pior, porque\n",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," \xe9 o termo que ",(0,s.jsx)(o.strong,{children:"domina a posi\xe7\xe3o da arma na tela"})," \u2014 ",(0,s.jsx)(o.code,{children:"public/js/game.js:555"}),"\ndeclara ",(0,s.jsx)(o.code,{children:"VM_OFF = [0.03, -0.1000, 0]"}),", e ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1163"})," mede a\nsensibilidade: ",(0,s.jsx)(o.em,{children:'"tirar o recuoZ move o grip 3,5 cm; tirar o VM_OFF move 23 cm"'}),"."]}),"\n",(0,s.jsx)(o.p,{children:"Zerar esse termo fechou duas invariantes e apagou a decis\xe3o est\xe9tica que o dono tinha\ntomado \u2014 que n\xe3o estava escrita em lugar nenhum que a r\xe9gua pudesse ler."}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"A corre\xe7\xe3o n\xe3o foi punir o agente. Foi escrever a inten\xe7\xe3o como invariante."})," Hoje\nexiste a VM12 (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:497"}),"): ",(0,s.jsx)(o.em,{children:'"look CS 1.6: boca do cano LOGO abaixo\nda mira (y entre 0,50 e 0,62) nos 2 aspectos"'}),". Com ela no lugar, a mesma otimiza\xe7\xe3o\nfica ",(0,s.jsx)(o.strong,{children:"vermelha"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"E a consequ\xeancia operacional, do mesmo coment\xe1rio:"}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Quem quiser mudar o look tem que mudar ",(0,s.jsx)(o.strong,{children:"ESTE teto"})," explicitamente, num diff que o\ndono v\xea, em vez de mexer no ",(0,s.jsx)(o.code,{children:"VM_OFF"}),' e reportar "+3 invariantes".']}),"\n"]}),"\n",(0,s.jsx)(o.admonition,{title:"O que isso significa pro seu PR",type:"tip",children:(0,s.jsxs)(o.p,{children:["Se a sua mudan\xe7a melhora o placar do quality gate, a primeira pergunta \xe9: ",(0,s.jsx)(o.strong,{children:"o que eu mudei que\no quality gate n\xe3o olha?"}),' Se a resposta for "o look", "o feel" ou "a sensa\xe7\xe3o", escreva a\ninvariante antes de mandar o PR \u2014 ou explique no PR por que ela n\xe3o cabe.']})}),"\n",(0,s.jsx)(o.h3,{id:"lei-2--teto-sem-proced\xeancia-\xe9-opini\xe3o",children:"Lei 2 \u2014 Teto sem proced\xeancia \xe9 opini\xe3o"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py:1-40"}),"."]})," Essa docstring \xe9 a doutrina da casa. O caso:"]}),"\n",(0,s.jsxs)(o.p,{children:["Durante ",(0,s.jsx)(o.strong,{children:"tr\xeas dias"})," o quality gate de armas foi resolvido contra n\xfameros ",(0,s.jsx)(o.strong,{children:"asseridos"}),":"]}),"\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsxs)(o.li,{children:["A VM12 exigia ",(0,s.jsx)(o.em,{children:'"boca do cano em y \u2265 0,66"'}),"."]}),"\n",(0,s.jsxs)(o.li,{children:["O doc do ",(0,s.jsx)(o.code,{children:"vmattach.js"})," dizia ",(0,s.jsx)(o.em,{children:'"coronha INTEIRA no canto"'}),"."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Nenhum dos dois foi medido em imagem nenhuma. Segundo ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:461-463"}),",\no piso 0,66 veio de um coment\xe1rio do ",(0,s.jsx)(o.code,{children:"public/js/vmattach.js"})," \u2014 ",(0,s.jsx)(o.em,{children:'"a boca fica a ~0,66H"'})," \u2014\nque por sua vez veio de um v\xeddeo assistido. (O coment\xe1rio do quality gate aponta para\n",(0,s.jsx)(o.code,{children:"vmattach.js:387-392"}),"; hoje o texto est\xe1 em ",(0,s.jsx)(o.code,{children:"vmattach.js:395"}),", porque o arquivo andou. \xc9\nexatamente o motivo de o ",(0,s.jsx)(o.code,{children:"ARCH.md"})," ser gerado \u2014 ver ",(0,s.jsx)(o.a,{href:"/docs/arquitetura",children:"Arquitetura"}),".)"]}),"\n",(0,s.jsxs)(o.p,{children:["O dono olhou o resultado e disse, literal (",(0,s.jsx)(o.code,{children:"ref-measure.py:14-17"}),"):"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:'"est\xe1 diferente do CS 1.6 e do Quake e do UT; nesses 3 a arma est\xe1 sempre no canto\ninferior direito e a coronha sempre FORA; depois de 3 dias e uma pasta inteira de\nrefer\xeancia nem voc\xea nem o Kimi entendeu isso."'}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["A\xed os frames foram ",(0,s.jsx)(o.strong,{children:"medidos"}),". ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py"})," faz segmenta\xe7\xe3o por cor no\nquadrante inferior-direito, pega a maior componente conexa, e escreve\n",(0,s.jsx)(o.code,{children:"tools/eval/ref_viewmodel.json"}),". Resultado:"]}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Frame"}),(0,s.jsx)(o.th,{children:"Boca (x, y)"}),(0,s.jsx)(o.th,{style:{textAlign:"right"},children:"\xc1rea na tela"}),(0,s.jsx)(o.th,{style:{textAlign:"right"},children:"\xc2ngulo do eixo"}),(0,s.jsx)(o.th,{children:"Cruza a borda direita?"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"cs16_ak_dust.jpg"})}),(0,s.jsxs)(o.td,{children:["0,564 ; ",(0,s.jsx)(o.strong,{children:"0,513"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"9,76%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"28,0\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"cs16_m4_dust.jpg"})}),(0,s.jsxs)(o.td,{children:["0,569 ; ",(0,s.jsx)(o.strong,{children:"0,598"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"9,78%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"34,8\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"valorant_vandal.jpg"})}),(0,s.jsxs)(o.td,{children:["0,648 ; ",(0,s.jsx)(o.strong,{children:"0,587"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"13,09%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"4,6\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]})]})]}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"Os dois n\xfameros asseridos estavam errados:"})}),"\n",(0,s.jsxs)(o.ol,{children:["\n",(0,s.jsxs)(o.li,{children:["A boca do CS 1.6 fica em ",(0,s.jsx)(o.strong,{children:"0,513\u20130,598"})," \u2014 logo abaixo da mira (0,5), 1 a 10 pontos\npercentuais abaixo do centro. N\xe3o em 0,66\u20130,93. O piso errado estava mantendo a nossa\narma ",(0,s.jsx)(o.strong,{children:"afundada"})," em 0,667\u20130,816 (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:472-475"}),")."]}),"\n",(0,s.jsxs)(o.li,{children:["A coronha ",(0,s.jsx)(o.strong,{children:"SAI pela quina"})," nos 3 frames. Sair \xe9 o padr\xe3o, n\xe3o o defeito\n(",(0,s.jsx)(o.code,{children:"ref_viewmodel.json"})," \u2192 ",(0,s.jsx)(o.code,{children:"faixas.cruzaBordaDireita: true"})," nos 3)."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["E o dano colateral: com o teto falso, o solver da rodada anterior ",(0,s.jsx)(o.em,{children:'"provou"'})," que 3% de\n\xe1rea era invi\xe1vel. A prova estava certa ",(0,s.jsx)(o.strong,{children:"contra aquele teto"})," \u2014 e o teto \xe9 que era falso\n(",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:476-478"}),")."]}),"\n",(0,s.jsxs)(o.p,{children:["A regra que ficou, ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py:21-22"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"TETO DE INVARIANTE S\xd3 ENTRA COM PROCED\xcaNCIA \u2014 arquivo de refer\xeancia, pixel medido, e\neste script reproduzindo o n\xfamero. N\xfamero sem imagem \xe9 opini\xe3o."})}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Hoje as invariantes de enquadramento carregam a proced\xeancia no pr\xf3prio texto: VM1 (faixa\n0,50\u20130,60, ref 0,520\u20130,565), VM3 (22\u201342\xb0, ref 28,0\xb0 e 34,8\xb0), VM5 (6\u201316%, ref\n9,76\u201313,09%), VM12 (0,50\u20130,62, ref 0,513\u20130,598), VM16 (fatia na borda direita 0,02\u20130,20,\nref 0,053\u20130,095)."}),"\n",(0,s.jsx)(o.admonition,{title:"Proced\xeancia inclui admitir o que a imagem N\xc3O mede",type:"note",children:(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:599-602"}),' recusa criar um teto para "quanto da arma fica fora\ndo quadro", porque o que est\xe1 fora \xe9 invis\xedvel na foto \u2014 n\xe3o d\xe1 pra saber se a coronha do\nAK termina 5 cm ou 50 cm al\xe9m da borda. Os n\xfameros continuam no JSON como ',(0,s.jsx)(o.strong,{children:"evid\xeancia,\nsem gate"}),". Isso \xe9 proced\xeancia levada a s\xe9rio: a r\xe9gua diz onde ela para de saber."]})}),"\n",(0,s.jsxs)(o.p,{children:["E o mesmo rigor morde quem escreveu a r\xe9gua, no caso mais desconfort\xe1vel poss\xedvel: ",(0,s.jsx)(o.strong,{children:"as\nfotos de refer\xeancia de personagem chegaram, foram medidas, e foram REPROVADAS pela pr\xf3pria\nr\xe9gua."})," ",(0,s.jsx)(o.code,{children:"tools/eval/char-probe.mjs:25-45"})," conta o epis\xf3dio inteiro \u2014 ",(0,s.jsx)(o.code,{children:"references/funkeiros/"}),"\ntem 23 arquivos e ",(0,s.jsx)(o.code,{children:"references/palhacos/"})," tem 21, todos passados pelo ",(0,s.jsx)(o.code,{children:"ref-body.py"}),", com as\nm\xe1scaras ",(0,s.jsx)(o.strong,{children:"olhadas"})," (",(0,s.jsx)(o.code,{children:"--masks"}),"). O veredito, dito na cara pelo pr\xf3prio coment\xe1rio: s\xe3o\nselfies e closes; a segmenta\xe7\xe3o heur\xedstica devolve a m\xe3o, um peda\xe7o de jaqueta ou o cabelo\nde outra pessoa no fundo, e a raz\xe3o ombro/altura sai entre ",(0,s.jsx)(o.strong,{children:"0,42 e 3,78"})," quando um humano\nmede 0,259. Sobra ~1 foto de corpo inteiro utiliz\xe1vel \u2014 n\xe3o \xe9 amostra."]}),"\n",(0,s.jsxs)(o.p,{children:["O ",(0,s.jsx)(o.code,{children:"ref-body.py"})," exige ",(0,s.jsx)(o.strong,{children:"6 fotos aceitas"})," para um teto virar medido, e ele ",(0,s.jsx)(o.strong,{children:"diz por que\nn\xe3o virou"}),". Ent\xe3o o teto absoluto do CHR1 continua sendo ",(0,s.jsx)(o.strong,{children:"fallback publicado"})," (Drillis &\nContini 1966, via Winter), declarado como tal no campo ",(0,s.jsx)(o.code,{children:"procedencia"})," do JSON e na coluna do\nrelat\xf3rio."]}),"\n",(0,s.jsxs)(o.p,{children:["Repare no que isso significa: ter a foto ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," \xe9 ter a medi\xe7\xe3o. Foi mais f\xe1cil aceitar\nque os dados eram ruins do que promover uma medi\xe7\xe3o fr\xe1gil a teto \u2014 e essa \xe9 a Lei 2\naplicada contra o interesse de quem escreveu a r\xe9gua."]}),"\n",(0,s.jsxs)(o.admonition,{type:"warning",children:[(0,s.jsxs)(o.mdxAdmonitionTitle,{children:[(0,s.jsx)(o.code,{children:"references/"})," N\xc3O vem no clone \u2014 e isso \xe9 decis\xe3o, n\xe3o descuido"]}),(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"git ls-files references"})," devolve ",(0,s.jsx)(o.strong,{children:"zero"}),'. Em 04/08/2026 a pasta inteira foi\ndestrackeada por decis\xe3o do dono ("podem ficar local o references porque vamos construir\nlocal"): s\xe3o as telas-alvo da UI e os frames de refer\xeancia do viewmodel, e ficam s\xf3 na\nm\xe1quina dele.']}),(0,s.jsxs)(o.p,{children:["O que ",(0,s.jsx)(o.strong,{children:"sobrevive ao clone s\xe3o os N\xdaMEROS medidos delas"}),": ",(0,s.jsx)(o.code,{children:"tools/eval/ref_ui.json"})," e\n",(0,s.jsx)(o.code,{children:"tools/eval/ref_viewmodel.json"})," est\xe3o versionados. Esse \xe9 o contrato \u2014 se uma r\xe9gua sua\nprecisar rodar em CI, ela l\xea o JSON, nunca o PNG. R\xe9gua que abre imagem de\n",(0,s.jsx)(o.code,{children:"references/"})," fica vermelha em toda m\xe1quina que n\xe3o seja a do dono, e vermelha por\nambiente \xe9 a pior esp\xe9cie: ensina quem trabalha aqui a ignorar vermelho."]})]}),"\n",(0,s.jsx)(o.h2,{id:"teste-de-muta\xe7\xe3o-da-pr\xf3pria-r\xe9gua",children:"Teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua"}),"\n",(0,s.jsx)(o.p,{children:"Esta \xe9 a parte que quase nenhum projeto tem, e \xe9 onde este reposit\xf3rio \xe9 genuinamente\ndiferente."}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"Um quality gate que n\xe3o se mexe quando voc\xea quebra o c\xf3digo de prop\xf3sito est\xe1 cego."})}),"\n",(0,s.jsxs)(o.p,{children:["O jeito de descobrir isso \xe9 mutar: pegue o c\xf3digo corrigido, ",(0,s.jsx)(o.strong,{children:"desfa\xe7a a corre\xe7\xe3o de\nprop\xf3sito"}),", rode o quality gate, e veja se ele fica vermelho. Se ficar verde, o quality gate n\xe3o\nest\xe1 medindo o que voc\xea acha que ele mede."]}),"\n",(0,s.jsx)(o.h3,{id:"o-caso-2022-verde-com-a-corre\xe7\xe3o-removida",children:"O caso: 20/22 verde com a corre\xe7\xe3o removida"}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:910-920"}),"."]})}),"\n",(0,s.jsxs)(o.p,{children:["O contexto: ",(0,s.jsx)(o.code,{children:"public/js/game.js:577"})," declara"]}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-js",children:"const vmOffY = (aspect) => VM_OFF[1] * ((16 / 9) / (aspect || 16 / 9));\n"})}),"\n",(0,s.jsxs)(o.p,{children:["\xc9 a corre\xe7\xe3o de enquadramento vertical por aspecto \u2014 o motivo de a arma ficar no mesmo\nlugar em 16:9 e em 3:2 (o dono joga em 3:2). Ela \xe9 ",(0,s.jsx)(o.strong,{children:"chamada"})," no argumento Y de\n",(0,s.jsx)(o.code,{children:"this.vm.root.position.set(...)"}),", em ",(0,s.jsx)(o.code,{children:"public/js/game.js:4873"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"O buraco, medido em 08/2026:"}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["a etapa ",(0,s.jsx)(o.code,{children:"vmOff"})," conferia s\xf3 ",(0,s.jsx)(o.code,{children:"/this\\.vm\\.root\\.position\\.set\\(\\s*VM_OFF\\[0\\]/"})," \u2014 o termo\nX. O termo Y n\xe3o era conferido por ningu\xe9m, e o auditor (",(0,s.jsx)(o.code,{children:"vm-mint-audit.mjs:196"}),",\n",(0,s.jsx)(o.code,{children:"loadOffYFn"}),") l\xea a ",(0,s.jsx)(o.strong,{children:"DECLARA\xc7\xc3O"})," ",(0,s.jsx)(o.code,{children:"const vmOffY = (aspect) => ..."})," por regex ",(0,s.jsx)(o.strong,{children:"sem nunca\nperguntar se algu\xe9m a CHAMA"}),"."]}),"\n",(0,s.jsxs)(o.p,{children:["Resultado: trocando no ",(0,s.jsx)(o.code,{children:"game.js"})," a chamada ",(0,s.jsx)(o.code,{children:"vmOffY(...)"})," por ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," no argumento Y\n\u2014 isto \xe9, ",(0,s.jsx)(o.strong,{children:"removendo por inteiro a corre\xe7\xe3o de enquadramento vertical por aspecto"})," \u2014 o\nquality gate inteiro seguia ",(0,s.jsx)(o.strong,{children:"VERDE (20/22, com VM9, VM10, VM12 e VM15 todas verdes)"}),". Um\nquality gate que n\xe3o distingue o build corrigido do build sem a corre\xe7\xe3o n\xe3o est\xe1 medindo\nnada."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Repare no mecanismo do erro, porque ele se repete em qualquer linguagem: ",(0,s.jsxs)(o.strong,{children:["a invariante\nlia a ",(0,s.jsx)(o.em,{children:"declara\xe7\xe3o"})," de uma constante, e n\xe3o o ",(0,s.jsx)(o.em,{children:"uso"}),"."]})," Declarar e n\xe3o chamar \xe9 o jeito mais\nbarato de uma corre\xe7\xe3o sumir com o quality gate verde."]}),"\n",(0,s.jsxs)(o.p,{children:["O conserto foi cir\xfargico e vale copiar. A AUD1 hoje separa os tr\xeas argumentos do\n",(0,s.jsx)(o.code,{children:"position.set(...)"})," com um ",(0,s.jsx)(o.strong,{children:"varredor de par\xeanteses"})," \u2014 n\xe3o ",(0,s.jsx)(o.code,{children:"split(',')"}),", que cortaria\ndentro da chamada de fun\xe7\xe3o \u2014 e exige ",(0,s.jsx)(o.strong,{children:"nominalmente"})," que o argumento Y chame ",(0,s.jsx)(o.code,{children:"vmOffY("}),".\nE fecha o outro caminho junto (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1148-1151"}),"): a f\xf3rmula do\n",(0,s.jsx)(o.code,{children:"vmOffY"})," \xe9 ",(0,s.jsxs)(o.strong,{children:["lida do ",(0,s.jsx)(o.code,{children:"game.js"})," e avaliada"]})," em 16/9, e tem que dar exatamente ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"}),"."]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Os dois cheques juntos cobrem os dois jeitos de a corre\xe7\xe3o sumir: ",(0,s.jsx)(o.strong,{children:"apagar a CHAMADA"}),"\n(muta\xe7\xe3o medida em 08/2026) ou ",(0,s.jsx)(o.strong,{children:"adulterar a F\xd3RMULA"}),"."]}),"\n"]}),"\n",(0,s.jsx)(o.h3,{id:"n\xe3o-foi-um-caso-isolado--foram-tr\xeas",children:"N\xe3o foi um caso isolado \u2014 foram tr\xeas"}),"\n",(0,s.jsx)(o.p,{children:"O mesmo buraco apareceu em outros dois lugares, e cada um virou uma etapa nova da AUD1:"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Muta\xe7\xe3o"}),(0,s.jsx)(o.th,{children:"Placar com a corre\xe7\xe3o desfeita"}),(0,s.jsx)(o.th,{children:"Causa do falso verde"}),(0,s.jsx)(o.th,{children:"Onde"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Trocar ",(0,s.jsx)(o.code,{children:"vmOffY(...)"})," por ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," no argumento Y"]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.strong,{children:"20/22 verde"})}),(0,s.jsx)(o.td,{children:"a invariante lia a declara\xe7\xe3o, n\xe3o o uso"}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:910-920"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Trocar ",(0,s.jsx)(o.code,{children:"g.rotation.set(pit, yaw, t.roll)"})," por ",(0,s.jsx)(o.code,{children:"g.rotation.set(0, 0, t.roll)"})]}),(0,s.jsx)(o.td,{children:"verde"}),(0,s.jsxs)(o.td,{children:["a tabela ",(0,s.jsx)(o.code,{children:"VM_FRAME.cls"})," continua com os \xe2ngulos, e os tr\xeas espelhos continuam batendo ",(0,s.jsx)(o.strong,{children:"entre si"})]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:932-944"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Apagar ",(0,s.jsx)(o.code,{children:"* (weaponCFG(id).vm ?? 1)"})," da escala do mesh"]}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.strong,{children:"28/37 verde, AUD1 inclusive"}),' ("pior \u0394escala 0.0004")']}),(0,s.jsxs)(o.td,{children:["as duas pontas leem ",(0,s.jsx)(o.code,{children:"vm"})," de ",(0,s.jsx)(o.code,{children:"weapons.js"}),"; ",(0,s.jsxs)(o.strong,{children:["o ",(0,s.jsx)(o.code,{children:"game.js"})," nunca \xe9 perguntado"]})," \u2014 era o auditor conferindo a si mesmo"]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:971-975"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Mutar ",(0,s.jsx)(o.code,{children:"this._adsPose['pistol']"})]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.strong,{children:"20/22 verde"})}),(0,s.jsx)(o.td,{children:"o ADS n\xe3o tinha invariante nenhuma"}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:1185"})})]})]})]}),"\n",(0,s.jsx)(o.p,{children:"O padr\xe3o comum das quatro \xe9 o mesmo, e \xe9 o que voc\xea deve procurar na sua invariante:"}),"\n",(0,s.jsx)(o.admonition,{title:"O padr\xe3o do falso verde",type:"danger",children:(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"A r\xe9gua est\xe1 conferindo uma c\xf3pia da regra em vez do jogo."})," Seja porque l\xea a declara\xe7\xe3o\ne n\xe3o o uso, seja porque compara dois espelhos que leem a mesma fonte, seja porque a\ntabela de par\xe2metros continua correta enquanto ningu\xe9m a aplica. Se as duas pontas da sua\ncompara\xe7\xe3o puderem ficar consistentes ",(0,s.jsx)(o.strong,{children:"sem passar pelo c\xf3digo de produ\xe7\xe3o"}),", sua\ninvariante est\xe1 cega."]})}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/mat-check.mjs:18-27"})," resolve isso da forma mais direta poss\xedvel: o corpo do\n",(0,s.jsx)(o.code,{children:"fixVmMaterials"})," \xe9 ",(0,s.jsxs)(o.strong,{children:["recortado do ",(0,s.jsx)(o.code,{children:"game.js"})," e executado"]})," sobre um material-sonda. Se o\nc\xf3digo mudar, a r\xe9gua muda junto. ",(0,s.jsx)(o.em,{children:'"uma r\xe9gua que carrega uma C\xd3PIA da regra mente no dia\nem que a regra muda."'})]}),"\n",(0,s.jsxs)(o.h3,{id:"muta\xe7\xe3o-como-coisa-de-primeira-classe-ui-checkmjs",children:["Muta\xe7\xe3o como coisa de primeira classe: ",(0,s.jsx)(o.code,{children:"ui-check.mjs"})]}),"\n",(0,s.jsxs)(o.p,{children:["O arn\xeas de UI tem uma ",(0,s.jsx)(o.strong,{children:"tabela de muta\xe7\xf5es versionada"}),", e cada uma declara qual quality gate\ntem que ficar vermelho. ",(0,s.jsx)(o.code,{children:"tools/eval/ui-check.mjs:1046-1050"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Cada muta\xe7\xe3o DESFAZ um dos consertos desta rodada (ou fura um quality gate de prop\xf3sito) e diz\nqual quality gate TEM que ficar vermelho. ",(0,s.jsx)(o.strong,{children:"Uma r\xe9gua que n\xe3o reprova a vers\xe3o anterior do\npr\xf3prio arquivo n\xe3o \xe9 r\xe9gua, \xe9 decora\xe7\xe3o."})]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Rodar uma:"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # espera UI1 VERMELHA\nMUT=ui3_prompt_na_mira node tools/eval/ui-check.mjs # espera UI3 VERMELHA\nMUT=ui2_prompt_eterno node tools/eval/ui-check.mjs # espera UI2 VERMELHA\nMUT=ui4_ctf_sem_relogio node tools/eval/ui-check.mjs # espera UI4 VERMELHA\n"})}),"\n",(0,s.jsxs)(o.p,{children:["As 7 muta\xe7\xf5es est\xe3o em ",(0,s.jsx)(o.code,{children:"tools/eval/ui-check.mjs:1051-1134"}),". Duas mec\xe2nicas: ",(0,s.jsx)(o.code,{children:"css"})," reescreve\no ",(0,s.jsx)(o.code,{children:"public/style.css"})," ",(0,s.jsx)(o.strong,{children:"lido em mem\xf3ria"})," (nunca em disco \u2014 outros agentes est\xe3o editando o\narquivo agora), e ",(0,s.jsx)(o.code,{children:"sim"})," monkey-patcha o objeto ",(0,s.jsx)(o.code,{children:"Game"})," j\xe1 bootado. Se a muta\xe7\xe3o ",(0,s.jsx)(o.code,{children:"css"})," n\xe3o\ncasar com nada, o script sai com c\xf3digo 2 dizendo ",(0,s.jsx)(o.em,{children:'"o CSS mudou de forma"'})," \u2014 porque uma\nmuta\xe7\xe3o que n\xe3o aplica tamb\xe9m \xe9 um falso verde (",(0,s.jsx)(o.code,{children:"ui-check.mjs:1164"}),")."]}),"\n",(0,s.jsx)(o.h2,{id:"como-escrever-uma-invariante",children:"Como escrever uma invariante"}),"\n",(0,s.jsx)(o.p,{children:"Checklist, na ordem:"}),"\n",(0,s.jsxs)(o.ol,{children:["\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Comece pela frase do defeito."})," Literal, com as palavras de quem reclamou. Todo\narn\xeas desta base come\xe7a assim, e n\xe3o \xe9 estilo: \xe9 o que impede a invariante de medir\noutra coisa. Ver o cabe\xe7alho de ",(0,s.jsx)(o.code,{children:"tools/eval/map-check.mjs:5-12"})," \u2014 cinco frases do dono,\ncinco invariantes."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Traduza para uma grandeza mensur\xe1vel."}),' "os jogadores est\xe3o SUBMERSOS EMBAIXO DA\nEST\xc1TUA" \u2192 ',(0,s.jsx)(o.em,{children:"existe geometria vis\xedvel do mapa cujo topo passa de 0,30 m acima do ch\xe3o\nlocal naquele ponto"})," (MAP1). Note que a defini\xe7\xe3o operacional inclui ",(0,s.jsx)(o.strong,{children:"por que 0,30 m"}),':\n\xe9 o degrau que o corpo sobe; acima disso n\xe3o \xe9 "passar por cima", \xe9 "estar dentro".']}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Ache a proced\xeancia do teto."})," Arquivo de refer\xeancia + pixel medido + script que\nreproduz. Se n\xe3o existir, ",(0,s.jsx)(o.strong,{children:"diga que \xe9 fallback"})," e cite a fonte publicada, como o C1\nfaz. Nunca invente o n\xfamero."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Me\xe7a o c\xf3digo de produ\xe7\xe3o, n\xe3o uma c\xf3pia dele."})," Importe o m\xf3dulo real, recorte a\nfun\xe7\xe3o do arquivo e execute, ou exija nominalmente a chamada no texto do fonte."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Mute e confirme que fica vermelha."})," Desfa\xe7a a corre\xe7\xe3o que voc\xea acabou de fazer e\nrode o quality gate. Se ficar verde, sua invariante est\xe1 cega \u2014 volte pro passo 4. Se der pra\nautomatizar, registre a muta\xe7\xe3o numa tabela, como o ",(0,s.jsx)(o.code,{children:"ui-check.mjs"})," faz."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Escreva a evid\xeancia, n\xe3o s\xf3 o booleano."})," O quarto argumento do ",(0,s.jsx)(o.code,{children:"put()"})," \xe9 o que\nalgu\xe9m vai ler daqui a tr\xeas meses: ",(0,s.jsx)(o.code,{children:'"0,504 a 0,619 da altura em 52 medidas | 0 fora da faixa"'})," \xe9 \xfatil; ",(0,s.jsx)(o.code,{children:'"ok"'})," n\xe3o \xe9."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Escreva o coment\xe1rio de proced\xeancia acima dela."})," Em portugu\xeas, dizendo o que\naconteceu quando o n\xfamero estava errado. \xc9 esse coment\xe1rio que impede a pr\xf3xima rodada\nde refazer o erro."]}),"\n"]}),"\n",(0,s.jsx)(o.h3,{id:"anti-padr\xf5es-que-j\xe1-custaram-caro-aqui",children:"Anti-padr\xf5es que j\xe1 custaram caro aqui"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Anti-padr\xe3o"}),(0,s.jsx)(o.th,{children:"O que deu"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Ler a declara\xe7\xe3o de uma constante em vez do uso"}),(0,s.jsx)(o.td,{children:"20/22 verde com a corre\xe7\xe3o removida"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Dois espelhos que leem a mesma fonte"}),(0,s.jsx)(o.td,{children:'28/37 verde, "pior \u0394escala 0.0004", com o knob desligado'})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Adaptador de formato quebrado em sil\xeancio"}),(0,s.jsxs)(o.td,{children:["VM1\u2013VM6 ficaram ",(0,s.jsx)(o.strong,{children:"PULADAS desde que o auditor existe"})," \u2014 6 invariantes de viewmodel que nunca rodaram uma vez (",(0,s.jsx)(o.code,{children:"invariants.mjs:121-127"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Medir o v\xe3o contra o ch\xe3o local errado"}),(0,s.jsxs)(o.td,{children:["pickup dentro da piscina reportava v\xe3o ",(0,s.jsx)(o.strong,{children:"0,0000 \u2014 VERDE"})," (",(0,s.jsx)(o.code,{children:"pickup-check.mjs:20-23"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"waypoint \u2264 3 m" como proxy de alcance'}),(0,s.jsxs)(o.td,{children:["74 falsos-positivos e verde em bols\xe3o fechado (",(0,s.jsx)(o.code,{children:"pickup-check.mjs:34-42"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Piso sem teto"}),(0,s.jsxs)(o.td,{children:['"boca \u2265 0,66" aceita a boca em 0,95 (arma no por\xe3o) \u2014 foi assim que chegamos a 0,816 (',(0,s.jsx)(o.code,{children:"invariants.mjs:432-434"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Medir num mundo onde o defeito n\xe3o pode acontecer"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"eval:site"})," cobre ",(0,s.jsx)(o.code,{children:"/ranking"})," e ",(0,s.jsx)(o.strong,{children:"checa corpo"}),", e passou um dia inteiro verde com a p\xe1gina servindo ",(0,s.jsx)(o.strong,{children:"200 com 0 bytes"})," em produ\xe7\xe3o: ele sobe um ",(0,s.jsx)(o.code,{children:"astro dev"})," local, onde ",(0,s.jsx)(o.code,{children:"public/js"})," existe e o ",(0,s.jsx)(o.code,{children:"ENOENT"})," n\xe3o ocorre (BUG-49)"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Aceitar status como prova de p\xe1gina viva"}),(0,s.jsxs)(o.td,{children:["o mesmo BUG-49: ",(0,s.jsx)(o.code,{children:"status === 200"})," chamava de saud\xe1vel uma casca vazia. Corpo agora \xe9 cobrado por ",(0,s.jsx)(o.strong,{children:"tamanho"})]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"N\xfamero medido que ningu\xe9m reprova"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"gl-metrics.mjs"})," media calls/tri\xe2ngulos desde a rodada 3 e nenhuma cl\xe1usula lia o resultado; o teto s\xf3 existia como prosa num coment\xe1rio. o estacionamento da Loja H (",(0,s.jsx)(o.code,{children:"loja_h"}),") chegou a 4.347 calls antes de algu\xe9m olhar"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Policiar artefato em vez de fonte"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"mapa-id-check"})," varria ",(0,s.jsx)(o.code,{children:"public/docs/"})," (sa\xedda do Docusaurus) e ficava vermelha quando o bundle publicado estava uma gera\xe7\xe3o atr\xe1s de um rename \u2014 vermelho sem defeito"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"R\xe9gua que se acusa"}),(0,s.jsx)(o.td,{children:"a mesma: ela precisa citar os ids antigos para cobr\xe1-los, e se varria a si mesma. Nove ocorr\xeancias, todas dela"})]})]})]}),"\n",(0,s.jsx)(o.h2,{id:"esta-p\xe1gina-\xe9-a-doutrina-o-passo-a-passo-\xe9-uma-skill",children:"Esta p\xe1gina \xe9 a doutrina. O passo a passo \xe9 uma skill"}),"\n",(0,s.jsxs)(o.p,{children:["O que fazer, na ordem, quando algu\xe9m reporta um defeito \u2014 reproduzir, medir antes de\nconsertar, refutar o palpite \xf3bvio, mutar a r\xe9gua, rodar o quality gate na ordem certa e reportar\no que ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," foi verificado \u2014 est\xe1 em ",(0,s.jsx)(o.code,{children:".claude/skills/bug-hunt/SKILL.md"}),", com o caso real\nque comprou cada regra. Ela \xe9 escrita para agente ",(0,s.jsx)(o.strong,{children:"e"})," para gente, e aponta de volta para\nesta p\xe1gina em vez de repeti-la."]}),"\n",(0,s.jsxs)(o.h2,{id:"port\xf5es-que-n\xe3o-cabem-no-check-e-por-qu\xea",children:["Port\xf5es que N\xc3O cabem no ",(0,s.jsx)(o.code,{children:"check"}),", e por qu\xea"]}),"\n",(0,s.jsxs)(o.p,{children:["Tr\xeas r\xe9guas exigem insumo que o port\xe3o r\xe1pido n\xe3o tem \u2014 navegador, ou o build pronto. Elas\nficam de fora de prop\xf3sito e s\xe3o passo de pr\xe9-deploy, junto do ",(0,s.jsx)(o.code,{children:"eval:boot"}),". Cada uma nasceu\nde um defeito que os port\xf5es existentes n\xe3o podiam ver:"]}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Comando"}),(0,s.jsx)(o.th,{children:"O que mede"}),(0,s.jsx)(o.th,{children:"O buraco que fechou"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:ssr"})}),(0,s.jsxs)(o.td,{children:["toda p\xe1gina ",(0,s.jsx)(o.code,{children:"prerender = false"})," entrega ",(0,s.jsx)(o.strong,{children:"corpo"}),", medido no artefato do build, entrando no diret\xf3rio da fun\xe7\xe3o (o cwd de produ\xe7\xe3o)"]}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"/mapa"}),", ",(0,s.jsx)(o.code,{children:"/ranking"})," e ",(0,s.jsx)(o.code,{children:"/u/*"})," serviram ",(0,s.jsx)(o.strong,{children:"200 com 0 bytes"})," por um dia. O ",(0,s.jsx)(o.code,{children:"eval:site"})," mede um ",(0,s.jsx)(o.code,{children:"astro dev"})," local, onde o defeito n\xe3o pode acontecer"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:cena"})}),(0,s.jsxs)(o.td,{children:["teto de draw calls e tri\xe2ngulos por frame, ",(0,s.jsx)(o.strong,{children:"por mapa"})]}),(0,s.jsx)(o.td,{children:"o n\xfamero era medido desde a rodada 3 e ningu\xe9m reprovava. Descobriu que o mapa da Quebrada custa 1.8 k calls e roda a metade do fps dos outros"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:mapid"})}),(0,s.jsx)(o.td,{children:"nenhum id no estilo Counter-Strike sobrevive, todo id antigo resolve, e toda pr\xe9via existe em disco"}),(0,s.jsxs)(o.td,{children:["renomear id sem renomear a imagem deixa cartaz quebrado no menu: 404 no navegador, ",(0,s.jsx)(o.strong,{children:"nada"})," no build"]})]})]})]}),"\n",(0,s.jsxs)(o.p,{children:["O teto do ",(0,s.jsx)(o.code,{children:"eval:cena"})," mora em ",(0,s.jsx)(o.code,{children:"tools/eval/cena-tetos.mjs"}),", importado tanto pela r\xe9gua de\nnavegador quanto pelas cl\xe1usulas ",(0,s.jsx)(o.code,{children:"CENA"})," do ",(0,s.jsx)(o.code,{children:"invariants.mjs"})," \u2014 um limiar, dois leitores. Dois\nn\xfameros para o mesmo conceito \xe9 o instrumento discordando de si, e isso j\xe1 custou uma rodada\ninteira aqui."]}),"\n",(0,s.jsx)(o.h2,{id:"rodar-o-quality-gate",children:"Rodar o quality gate"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"node tools/eval/invariants.mjs # tudo que roda sem browser\nnode tools/eval/invariants.mjs --json # sa\xedda pra m\xe1quina\nnpm run check # syntax + quality gate + vm + coice + bots\nnpm run check:fast # segundos \u2014 rode este primeiro, sempre\n\n# pr\xe9-deploy: exigem navegador ou build, e por isso ficam fora dos de cima\nnpm run eval:boot # o jogo ABRE?\nnpm run build && npm run eval:ssr # p\xe1gina SSR entrega corpo?\nnpm run eval:cena # custo de cena dentro do teto?\n"})}),"\n",(0,s.jsxs)(o.p,{children:["Fontes atuais de produ\xe7\xe3o, dados e d\xedvida conhecida: ",(0,s.jsx)(o.a,{href:"/docs/estado",children:"Estado atual"}),"."]})]})}function m(e={}){const{wrapper:o}={...(0,n.R)(),...e.components};return o?(0,s.jsx)(o,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453(e,o,a){a.d(o,{R:()=>i,x:()=>d});var r=a(6540);const s={},n=r.createContext(s);function i(e){const o=r.useContext(n);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function d(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),r.createElement(n.Provider,{value:o},e.children)}}}]);
\ No newline at end of file
+"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[475],{1669(e,o,a){a.r(o),a.d(o,{assets:()=>c,contentTitle:()=>d,default:()=>m,frontMatter:()=>i,metadata:()=>r,toc:()=>t});const r=JSON.parse('{"id":"quality-gates","title":"O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o","description":"O que \xe9 uma invariante neste repo, como se escreve uma, as duas leis da casa com o caso real de cada uma, e o teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua.","source":"@site/docs/quality-gates.md","sourceDirName":".","slug":"/quality-gates","permalink":"/docs/quality-gates","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/quality-gates.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"id":"quality-gates","title":"O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o","sidebar_label":"Quality gates","sidebar_position":4,"description":"O que \xe9 uma invariante neste repo, como se escreve uma, as duas leis da casa com o caso real de cada uma, e o teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua."},"sidebar":"dev","previous":{"title":"Instrumenta\xe7\xe3o de IA","permalink":"/docs/instrumentacao-ai"},"next":{"title":"BotBrain","permalink":"/docs/botbrain"}}');var s=a(4848),n=a(8453);const i={id:"quality-gates",title:"O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o",sidebar_label:"Quality gates",sidebar_position:4,description:"O que \xe9 uma invariante neste repo, como se escreve uma, as duas leis da casa com o caso real de cada uma, e o teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua."},d="O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o",c={},t=[{value:"Por que ele existe",id:"por-que-ele-existe",level:2},{value:"O que \xe9 uma invariante aqui",id:"o-que-\xe9-uma-invariante-aqui",level:2},{value:"Severidade",id:"severidade",level:3},{value:"As duas leis da casa",id:"as-duas-leis-da-casa",level:2},{value:"Lei 1 \u2014 Inten\xe7\xe3o que n\xe3o vira invariante \xe9 otimizada para fora",id:"lei-1--inten\xe7\xe3o-que-n\xe3o-vira-invariante-\xe9-otimizada-para-fora",level:3},{value:"Lei 2 \u2014 Teto sem proced\xeancia \xe9 opini\xe3o",id:"lei-2--teto-sem-proced\xeancia-\xe9-opini\xe3o",level:3},{value:"Teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua",id:"teste-de-muta\xe7\xe3o-da-pr\xf3pria-r\xe9gua",level:2},{value:"O caso: 20/22 verde com a corre\xe7\xe3o removida",id:"o-caso-2022-verde-com-a-corre\xe7\xe3o-removida",level:3},{value:"N\xe3o foi um caso isolado \u2014 foram tr\xeas",id:"n\xe3o-foi-um-caso-isolado--foram-tr\xeas",level:3},{value:"Muta\xe7\xe3o como coisa de primeira classe: ui-check.mjs",id:"muta\xe7\xe3o-como-coisa-de-primeira-classe-ui-checkmjs",level:3},{value:"Como escrever uma invariante",id:"como-escrever-uma-invariante",level:2},{value:"Anti-padr\xf5es que j\xe1 custaram caro aqui",id:"anti-padr\xf5es-que-j\xe1-custaram-caro-aqui",level:3},{value:"Esta p\xe1gina \xe9 a doutrina. O passo a passo \xe9 uma skill",id:"esta-p\xe1gina-\xe9-a-doutrina-o-passo-a-passo-\xe9-uma-skill",level:2},{value:"Port\xf5es que N\xc3O cabem no check, e por qu\xea",id:"port\xf5es-que-n\xe3o-cabem-no-check-e-por-qu\xea",level:2},{value:"Rodar o quality gate",id:"rodar-o-quality-gate",level:2}];function l(e){const o={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,n.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.header,{children:(0,s.jsx)(o.h1,{id:"o-quality-gate-invariantes-proced\xeancia-e-muta\xe7\xe3o",children:"O quality gate: invariantes, proced\xeancia e muta\xe7\xe3o"})}),"\n",(0,s.jsxs)(o.p,{children:["O quality gate deste reposit\xf3rio \xe9 um arquivo: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),". Ele roda em node puro\ne sai com c\xf3digo 1 se qualquer invariante ",(0,s.jsx)(o.strong,{children:"cr\xedtica"})," falhar. \xc9 o que o CI executa em todo\nPR (",(0,s.jsx)(o.code,{children:".github/workflows/ci.yml"}),")."]}),"\n","\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs"}),": ",(0,s.jsx)(o.strong,{children:"2.275 linhas"}),", ",(0,s.jsx)(o.strong,{children:"65 identificadores de invariante declarados"})," (",(0,s.jsx)(o.code,{children:"put()"}),"), dos quais ",(0,s.jsx)(o.strong,{children:"28"})," t\xeam caminho de ",(0,s.jsx)(o.code,{children:"skip()"})," declarado."]}),"\n",(0,s.jsxs)(o.li,{children:["O arn\xeas inteiro s\xe3o ",(0,s.jsx)(o.strong,{children:"200 scripts"})," em ",(0,s.jsx)(o.code,{children:"tools/eval/"})," (",(0,s.jsx)(o.code,{children:".mjs"})," + ",(0,s.jsx)(o.code,{children:".py"}),"), mais ",(0,s.jsx)(o.strong,{children:"54 scripts"})," de pipeline em ",(0,s.jsx)(o.code,{children:"tools/"}),"."]}),"\n",(0,s.jsxs)(o.li,{children:["Quantas invariantes rodam como ",(0,s.jsx)(o.strong,{children:"cr\xedticas"})," numa execu\xe7\xe3o ",(0,s.jsx)(o.strong,{children:"n\xe3o \xe9 deriv\xe1vel do fonte"}),": depende de qual insumo existe na m\xe1quina (o JSON do auditor de viewmodel, um GLB, uma pasta de anims). Esse n\xfamero s\xf3 sai rodando o quality gate \u2014 e o lugar dele \xe9 o cabe\xe7alho do ",(0,s.jsx)(o.code,{children:"KNOWN-BUGS.md"}),", atualizado com sa\xedda real."]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Reproduza:"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\ngrep -o \"skip('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\n"})}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Bloco gerado por ",(0,s.jsx)(o.code,{children:"node tools/gen-docs.mjs"}),". Fonte: ",(0,s.jsx)(o.code,{children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l"})]}),"\n"]}),"\n","\n",(0,s.jsxs)(o.p,{children:["O terceiro item acima \xe9 a distin\xe7\xe3o que mais confunde quem chega: ",(0,s.jsx)(o.strong,{children:"identificador\ndeclarado \u2260 invariante avaliada."})," V\xe1rias viram ",(0,s.jsx)(o.code,{children:"skip"})," em vez de ",(0,s.jsx)(o.code,{children:"put"})," quando falta o\ninsumo delas (o JSON do auditor de viewmodel, um GLB, uma pasta de anims). ",(0,s.jsx)(o.code,{children:"skip"})," \xe9\n",(0,s.jsx)(o.strong,{children:"quality gate verde por aus\xeancia de dado"}),', e \xe9 por isso que ele sempre carrega o motivo. Ver\n"Severidade", abaixo.']}),"\n",(0,s.jsx)(o.p,{children:"Esta p\xe1gina \xe9 a mais \xfatil do site. Se voc\xea s\xf3 for ler uma, leia esta."}),"\n",(0,s.jsx)(o.h2,{id:"por-que-ele-existe",children:"Por que ele existe"}),"\n",(0,s.jsxs)(o.p,{children:["Do cabe\xe7alho do pr\xf3prio arquivo, ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:5-19"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["O dono passou 3 dias num ciclo em que cada rodada consertava uma coisa e quebrava\noutra, e a gente s\xf3 descobria uma rodada depois. A causa n\xe3o era falta de cuidado:\nera falta de R\xc9GUA. Um cr\xedtico (humano ou agente) julga screenshot; consist\xeancia e\nflow s\xe3o propriedades do jogo ",(0,s.jsx)(o.strong,{children:"EM MOVIMENTO"}),", e quase todo defeito que ele reportou\nn\xe3o \xe9 gosto \u2014 \xe9 invariante violada."]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"E a tradu\xe7\xe3o, que \xe9 a coisa mais importante deste reposit\xf3rio inteiro:"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"O que o dono disse"}),(0,s.jsx)(o.th,{children:"Qual invariante isso virou"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"as m\xe3os est\xe3o soltas no ar"'}),(0,s.jsx)(o.td,{children:"dist\xe2ncia m\xe3o\u2194grip tem um teto"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"a arma aponta pra baixo"'}),(0,s.jsx)(o.td,{children:"o cano tem um \xe2ngulo m\xe1ximo"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"no ADS n\xe3o vejo a arma nem a mira"'}),(0,s.jsx)(o.td,{children:"a arma tem \xe1rea m\xednima e m\xe1xima"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"sniper sem zoom"'}),(0,s.jsx)(o.td,{children:"FOV mirando < FOV de quadril"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"v\xe1rias armas com visual igual"'}),(0,s.jsx)(o.td,{children:"silhuetas t\xeam que diferir"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"o bot atira do nada"'}),(0,s.jsx)(o.td,{children:"dano exige LOS anterior"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"tem 2 me eliminando"'}),(0,s.jsx)(o.td,{children:"1 killfeed por morte"})]})]})]}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:20-21"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"REGRA DE OURO: nada \xe9 commitado com invariante VERMELHA. E todo bug novo que o dono\nreportar vira uma invariante aqui \u2014 \xe9 assim que ele nunca volta."})}),"\n"]}),"\n",(0,s.jsx)(o.h2,{id:"o-que-\xe9-uma-invariante-aqui",children:"O que \xe9 uma invariante aqui"}),"\n",(0,s.jsxs)(o.p,{children:["Uma invariante \xe9 uma ",(0,s.jsx)(o.strong,{children:"propriedade do jogo que d\xe1 pra medir sem um humano olhando"}),", com\num teto ou uma faixa que tem proced\xeancia. N\xe3o \xe9 teste unit\xe1rio: quase nenhuma invariante\ntesta uma fun\xe7\xe3o. Elas medem o ",(0,s.jsx)(o.strong,{children:"estado do jogo rodando de verdade"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"Tr\xeas formas, todas presentes no arquivo:"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"1. Lida do c\xf3digo-fonte."})," Barata, roda em milissegundos, pega classes inteiras de bug.\nExemplo real, ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1439-1446"}),":"]}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-js",children:"// ARM1 \u2014 toda arma com luneta precisa de zoom de verdade. \"Snipers sem zoom\"\n// \xe9 reclama\xe7\xe3o literal; a solu\xe7\xe3o N\xc3O \xe9 tirar a luneta, \xe9 fazer a certa.\nconst bloco = gsrc.slice(0, gsrc.indexOf('};', gsrc.indexOf('const WEAPONS')) + 2);\nconst linhas = bloco.split('\\n').filter((l) => /^\\s*\\w+:\\s*\\{/.test(l));\nconst semZoom = linhas.filter((l) => /scope:\\s*true/.test(l) && !/spreadScope/.test(l))\n .map((l) => l.trim().split(':')[0]);\nput('ARM1', 'toda arma com scope:true declara spreadScope', semZoom.length === 0,\n semZoom.length ? semZoom.join(', ') : `${linhas.length} armas conferidas`);\n"})}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"2. Medida no jogo real rodando em node."})," ",(0,s.jsx)(o.code,{children:"tools/eval/harness.mjs"})," sobe a classe ",(0,s.jsx)(o.code,{children:"Game"}),"\nde verdade, com os mapas de verdade, com DOM/canvas stubado. \xc9 o ",(0,s.jsx)(o.strong,{children:"c\xf3digo de produ\xe7\xe3o"}),"\nque \xe9 medido, n\xe3o uma reimplementa\xe7\xe3o \u2014 ",(0,s.jsx)(o.code,{children:"tools/eval/botsim.mjs:8-9"}),": ",(0,s.jsx)(o.em,{children:'"se o n\xfamero\nmelhorar aqui, melhorou no jogo"'}),". Daqui saem BOT1\u2013BOT8, MAP1\u2013MAP3, CTF1, MAT1/MAT2,\nFOG1, TEX1, VM14, MOD1/MOD2."]}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"3. Medida na geometria dos assets."})," ",(0,s.jsx)(o.code,{children:"vm-mint-audit.mjs"})," abre todos os GLBs de arma com um\nparser de GLB pr\xf3prio e projeta o viewmodel na tela. Daqui saem VM1\u2013VM19."]}),"\n",(0,s.jsxs)(o.p,{children:["O que ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," cabe aqui: invariante que exige pixel de browser. Essas est\xe3o marcadas\n",(0,s.jsx)(o.code,{children:"browser"})," e s\xe3o puladas, com o motivo dito \u2014 SwiftShader custa ~4 min por carga de mapa\nnesta m\xe1quina (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:99"}),")."]}),"\n",(0,s.jsx)(o.h3,{id:"severidade",children:"Severidade"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"put(id, desc, ok, evid, sev)"})," aceita ",(0,s.jsx)(o.code,{children:"'crit'"})," (padr\xe3o) ou ",(0,s.jsx)(o.code,{children:"'warn'"}),"\n(",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:81-82"}),"). Cr\xedtica vermelha reprova o PR. Warn \xe9 ru\xeddo medido que\nalgu\xe9m precisa olhar mas n\xe3o bloqueia \u2014 \xe9 onde vivem BOT1/BOT2/BOT3/BOT6/BOT7, ARM4 e\nARM5. ",(0,s.jsx)(o.code,{children:"skip()"})," \xe9 o terceiro estado, e ele \xe9 ",(0,s.jsx)(o.strong,{children:"perigoso"}),": quality gate verde por aus\xeancia de\ndado. Por isso todo ",(0,s.jsx)(o.code,{children:"skip"})," carrega o motivo."]}),"\n",(0,s.jsx)(o.h2,{id:"as-duas-leis-da-casa",children:"As duas leis da casa"}),"\n",(0,s.jsx)(o.h3,{id:"lei-1--inten\xe7\xe3o-que-n\xe3o-vira-invariante-\xe9-otimizada-para-fora",children:"Lei 1 \u2014 Inten\xe7\xe3o que n\xe3o vira invariante \xe9 otimizada para fora"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:452-461"}),"."]})," O caso, literal:"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["a rodada anterior levou o quality gate de ",(0,s.jsx)(o.strong,{children:"16/21 para 19/21 sem afrouxar um teto sequer"})," e\nmesmo assim foi ",(0,s.jsx)(o.strong,{children:"REPROVADA"})," pelo dono, porque para fechar VM5/VM10 ela ",(0,s.jsxs)(o.strong,{children:["ZEROU o\n",(0,s.jsx)(o.code,{children:"VM_OFF"})," y"]}),' e mudou o look em sil\xeancio. Nenhuma invariante codificava "onde fica a\nboca do cano", ent\xe3o a m\xe9trica foi otimizada e a INTEN\xc7\xc3O foi destru\xedda. Lei de\nGoodhart, na \xedntegra. ',(0,s.jsx)(o.strong,{children:"INTEN\xc7\xc3O QUE N\xc3O VIRA INVARIANTE \xc9 OTIMIZADA PARA FORA."})]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Leia de novo o que aconteceu, porque \xe9 contraintuitivo: o agente ",(0,s.jsx)(o.strong,{children:"n\xe3o trapaceou"}),". Ele\nn\xe3o afrouxou nenhum teto. Ele subiu o placar de verdade. E o resultado foi pior, porque\n",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," \xe9 o termo que ",(0,s.jsx)(o.strong,{children:"domina a posi\xe7\xe3o da arma na tela"})," \u2014 ",(0,s.jsx)(o.code,{children:"public/js/game.js:555"}),"\ndeclara ",(0,s.jsx)(o.code,{children:"VM_OFF = [0.03, -0.1000, 0]"}),", e ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1163"})," mede a\nsensibilidade: ",(0,s.jsx)(o.em,{children:'"tirar o recuoZ move o grip 3,5 cm; tirar o VM_OFF move 23 cm"'}),"."]}),"\n",(0,s.jsx)(o.p,{children:"Zerar esse termo fechou duas invariantes e apagou a decis\xe3o est\xe9tica que o dono tinha\ntomado \u2014 que n\xe3o estava escrita em lugar nenhum que a r\xe9gua pudesse ler."}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"A corre\xe7\xe3o n\xe3o foi punir o agente. Foi escrever a inten\xe7\xe3o como invariante."})," Hoje\nexiste a VM12 (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:497"}),"): ",(0,s.jsx)(o.em,{children:'"look CS 1.6: boca do cano LOGO abaixo\nda mira (y entre 0,50 e 0,62) nos 2 aspectos"'}),". Com ela no lugar, a mesma otimiza\xe7\xe3o\nfica ",(0,s.jsx)(o.strong,{children:"vermelha"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"E a consequ\xeancia operacional, do mesmo coment\xe1rio:"}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Quem quiser mudar o look tem que mudar ",(0,s.jsx)(o.strong,{children:"ESTE teto"})," explicitamente, num diff que o\ndono v\xea, em vez de mexer no ",(0,s.jsx)(o.code,{children:"VM_OFF"}),' e reportar "+3 invariantes".']}),"\n"]}),"\n",(0,s.jsx)(o.admonition,{title:"O que isso significa pro seu PR",type:"tip",children:(0,s.jsxs)(o.p,{children:["Se a sua mudan\xe7a melhora o placar do quality gate, a primeira pergunta \xe9: ",(0,s.jsx)(o.strong,{children:"o que eu mudei que\no quality gate n\xe3o olha?"}),' Se a resposta for "o look", "o feel" ou "a sensa\xe7\xe3o", escreva a\ninvariante antes de mandar o PR \u2014 ou explique no PR por que ela n\xe3o cabe.']})}),"\n",(0,s.jsx)(o.h3,{id:"lei-2--teto-sem-proced\xeancia-\xe9-opini\xe3o",children:"Lei 2 \u2014 Teto sem proced\xeancia \xe9 opini\xe3o"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py:1-40"}),"."]})," Essa docstring \xe9 a doutrina da casa. O caso:"]}),"\n",(0,s.jsxs)(o.p,{children:["Durante ",(0,s.jsx)(o.strong,{children:"tr\xeas dias"})," o quality gate de armas foi resolvido contra n\xfameros ",(0,s.jsx)(o.strong,{children:"asseridos"}),":"]}),"\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsxs)(o.li,{children:["A VM12 exigia ",(0,s.jsx)(o.em,{children:'"boca do cano em y \u2265 0,66"'}),"."]}),"\n",(0,s.jsxs)(o.li,{children:["O doc do ",(0,s.jsx)(o.code,{children:"vmattach.js"})," dizia ",(0,s.jsx)(o.em,{children:'"coronha INTEIRA no canto"'}),"."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Nenhum dos dois foi medido em imagem nenhuma. Segundo ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:461-463"}),",\no piso 0,66 veio de um coment\xe1rio do ",(0,s.jsx)(o.code,{children:"public/js/vmattach.js"})," \u2014 ",(0,s.jsx)(o.em,{children:'"a boca fica a ~0,66H"'})," \u2014\nque por sua vez veio de um v\xeddeo assistido. (O coment\xe1rio do quality gate aponta para\n",(0,s.jsx)(o.code,{children:"vmattach.js:387-392"}),"; hoje o texto est\xe1 em ",(0,s.jsx)(o.code,{children:"vmattach.js:395"}),", porque o arquivo andou. \xc9\nexatamente o motivo de o ",(0,s.jsx)(o.code,{children:"ARCH.md"})," ser gerado \u2014 ver ",(0,s.jsx)(o.a,{href:"/docs/arquitetura",children:"Arquitetura"}),".)"]}),"\n",(0,s.jsxs)(o.p,{children:["O dono olhou o resultado e disse, literal (",(0,s.jsx)(o.code,{children:"ref-measure.py:14-17"}),"):"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:'"est\xe1 diferente do CS 1.6 e do Quake e do UT; nesses 3 a arma est\xe1 sempre no canto\ninferior direito e a coronha sempre FORA; depois de 3 dias e uma pasta inteira de\nrefer\xeancia nem voc\xea nem o Kimi entendeu isso."'}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["A\xed os frames foram ",(0,s.jsx)(o.strong,{children:"medidos"}),". ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py"})," faz segmenta\xe7\xe3o por cor no\nquadrante inferior-direito, pega a maior componente conexa, e escreve\n",(0,s.jsx)(o.code,{children:"tools/eval/ref_viewmodel.json"}),". Resultado:"]}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Frame"}),(0,s.jsx)(o.th,{children:"Boca (x, y)"}),(0,s.jsx)(o.th,{style:{textAlign:"right"},children:"\xc1rea na tela"}),(0,s.jsx)(o.th,{style:{textAlign:"right"},children:"\xc2ngulo do eixo"}),(0,s.jsx)(o.th,{children:"Cruza a borda direita?"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"cs16_ak_dust.jpg"})}),(0,s.jsxs)(o.td,{children:["0,564 ; ",(0,s.jsx)(o.strong,{children:"0,513"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"9,76%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"28,0\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"cs16_m4_dust.jpg"})}),(0,s.jsxs)(o.td,{children:["0,569 ; ",(0,s.jsx)(o.strong,{children:"0,598"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"9,78%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"34,8\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"valorant_vandal.jpg"})}),(0,s.jsxs)(o.td,{children:["0,648 ; ",(0,s.jsx)(o.strong,{children:"0,587"})]}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"13,09%"}),(0,s.jsx)(o.td,{style:{textAlign:"right"},children:"4,6\xb0"}),(0,s.jsx)(o.td,{children:"sim"})]})]})]}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"Os dois n\xfameros asseridos estavam errados:"})}),"\n",(0,s.jsxs)(o.ol,{children:["\n",(0,s.jsxs)(o.li,{children:["A boca do CS 1.6 fica em ",(0,s.jsx)(o.strong,{children:"0,513\u20130,598"})," \u2014 logo abaixo da mira (0,5), 1 a 10 pontos\npercentuais abaixo do centro. N\xe3o em 0,66\u20130,93. O piso errado estava mantendo a nossa\narma ",(0,s.jsx)(o.strong,{children:"afundada"})," em 0,667\u20130,816 (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:472-475"}),")."]}),"\n",(0,s.jsxs)(o.li,{children:["A coronha ",(0,s.jsx)(o.strong,{children:"SAI pela quina"})," nos 3 frames. Sair \xe9 o padr\xe3o, n\xe3o o defeito\n(",(0,s.jsx)(o.code,{children:"ref_viewmodel.json"})," \u2192 ",(0,s.jsx)(o.code,{children:"faixas.cruzaBordaDireita: true"})," nos 3)."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["E o dano colateral: com o teto falso, o solver da rodada anterior ",(0,s.jsx)(o.em,{children:'"provou"'})," que 3% de\n\xe1rea era invi\xe1vel. A prova estava certa ",(0,s.jsx)(o.strong,{children:"contra aquele teto"})," \u2014 e o teto \xe9 que era falso\n(",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:476-478"}),")."]}),"\n",(0,s.jsxs)(o.p,{children:["A regra que ficou, ",(0,s.jsx)(o.code,{children:"tools/eval/ref-measure.py:21-22"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"TETO DE INVARIANTE S\xd3 ENTRA COM PROCED\xcaNCIA \u2014 arquivo de refer\xeancia, pixel medido, e\neste script reproduzindo o n\xfamero. N\xfamero sem imagem \xe9 opini\xe3o."})}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Hoje as invariantes de enquadramento carregam a proced\xeancia no pr\xf3prio texto: VM1 (faixa\n0,50\u20130,60, ref 0,520\u20130,565), VM3 (22\u201342\xb0, ref 28,0\xb0 e 34,8\xb0), VM5 (6\u201316%, ref\n9,76\u201313,09%), VM12 (0,50\u20130,62, ref 0,513\u20130,598), VM16 (fatia na borda direita 0,02\u20130,20,\nref 0,053\u20130,095)."}),"\n",(0,s.jsx)(o.admonition,{title:"Proced\xeancia inclui admitir o que a imagem N\xc3O mede",type:"note",children:(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:599-602"}),' recusa criar um teto para "quanto da arma fica fora\ndo quadro", porque o que est\xe1 fora \xe9 invis\xedvel na foto \u2014 n\xe3o d\xe1 pra saber se a coronha do\nAK termina 5 cm ou 50 cm al\xe9m da borda. Os n\xfameros continuam no JSON como ',(0,s.jsx)(o.strong,{children:"evid\xeancia,\nsem gate"}),". Isso \xe9 proced\xeancia levada a s\xe9rio: a r\xe9gua diz onde ela para de saber."]})}),"\n",(0,s.jsxs)(o.p,{children:["E o mesmo rigor morde quem escreveu a r\xe9gua, no caso mais desconfort\xe1vel poss\xedvel: ",(0,s.jsx)(o.strong,{children:"as\nfotos de refer\xeancia de personagem chegaram, foram medidas, e foram REPROVADAS pela pr\xf3pria\nr\xe9gua."})," ",(0,s.jsx)(o.code,{children:"tools/eval/char-probe.mjs:25-45"})," conta o epis\xf3dio inteiro \u2014 ",(0,s.jsx)(o.code,{children:"references/funkeiros/"}),"\ntem 23 arquivos e ",(0,s.jsx)(o.code,{children:"references/palhacos/"})," tem 21, todos passados pelo ",(0,s.jsx)(o.code,{children:"ref-body.py"}),", com as\nm\xe1scaras ",(0,s.jsx)(o.strong,{children:"olhadas"})," (",(0,s.jsx)(o.code,{children:"--masks"}),"). O veredito, dito na cara pelo pr\xf3prio coment\xe1rio: s\xe3o\nselfies e closes; a segmenta\xe7\xe3o heur\xedstica devolve a m\xe3o, um peda\xe7o de jaqueta ou o cabelo\nde outra pessoa no fundo, e a raz\xe3o ombro/altura sai entre ",(0,s.jsx)(o.strong,{children:"0,42 e 3,78"})," quando um humano\nmede 0,259. Sobra ~1 foto de corpo inteiro utiliz\xe1vel \u2014 n\xe3o \xe9 amostra."]}),"\n",(0,s.jsxs)(o.p,{children:["O ",(0,s.jsx)(o.code,{children:"ref-body.py"})," exige ",(0,s.jsx)(o.strong,{children:"6 fotos aceitas"})," para um teto virar medido, e ele ",(0,s.jsx)(o.strong,{children:"diz por que\nn\xe3o virou"}),". Ent\xe3o o teto absoluto do CHR1 continua sendo ",(0,s.jsx)(o.strong,{children:"fallback publicado"})," (Drillis &\nContini 1966, via Winter), declarado como tal no campo ",(0,s.jsx)(o.code,{children:"procedencia"})," do JSON e na coluna do\nrelat\xf3rio."]}),"\n",(0,s.jsxs)(o.p,{children:["Repare no que isso significa: ter a foto ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," \xe9 ter a medi\xe7\xe3o. Foi mais f\xe1cil aceitar\nque os dados eram ruins do que promover uma medi\xe7\xe3o fr\xe1gil a teto \u2014 e essa \xe9 a Lei 2\naplicada contra o interesse de quem escreveu a r\xe9gua."]}),"\n",(0,s.jsxs)(o.admonition,{type:"warning",children:[(0,s.jsxs)(o.mdxAdmonitionTitle,{children:[(0,s.jsx)(o.code,{children:"references/"})," N\xc3O vem no clone \u2014 e isso \xe9 decis\xe3o, n\xe3o descuido"]}),(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"git ls-files references"})," devolve ",(0,s.jsx)(o.strong,{children:"zero"}),'. Em 04/08/2026 a pasta inteira foi\ndestrackeada por decis\xe3o do dono ("podem ficar local o references porque vamos construir\nlocal"): s\xe3o as telas-alvo da UI e os frames de refer\xeancia do viewmodel, e ficam s\xf3 na\nm\xe1quina dele.']}),(0,s.jsxs)(o.p,{children:["O que ",(0,s.jsx)(o.strong,{children:"sobrevive ao clone s\xe3o os N\xdaMEROS medidos delas"}),": ",(0,s.jsx)(o.code,{children:"tools/eval/ref_ui.json"})," e\n",(0,s.jsx)(o.code,{children:"tools/eval/ref_viewmodel.json"})," est\xe3o versionados. Esse \xe9 o contrato \u2014 se uma r\xe9gua sua\nprecisar rodar em CI, ela l\xea o JSON, nunca o PNG. R\xe9gua que abre imagem de\n",(0,s.jsx)(o.code,{children:"references/"})," fica vermelha em toda m\xe1quina que n\xe3o seja a do dono, e vermelha por\nambiente \xe9 a pior esp\xe9cie: ensina quem trabalha aqui a ignorar vermelho."]})]}),"\n",(0,s.jsx)(o.h2,{id:"teste-de-muta\xe7\xe3o-da-pr\xf3pria-r\xe9gua",children:"Teste de muta\xe7\xe3o da pr\xf3pria r\xe9gua"}),"\n",(0,s.jsx)(o.p,{children:"Esta \xe9 a parte que quase nenhum projeto tem, e \xe9 onde este reposit\xf3rio \xe9 genuinamente\ndiferente."}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.strong,{children:"Um quality gate que n\xe3o se mexe quando voc\xea quebra o c\xf3digo de prop\xf3sito est\xe1 cego."})}),"\n",(0,s.jsxs)(o.p,{children:["O jeito de descobrir isso \xe9 mutar: pegue o c\xf3digo corrigido, ",(0,s.jsx)(o.strong,{children:"desfa\xe7a a corre\xe7\xe3o de\nprop\xf3sito"}),", rode o quality gate, e veja se ele fica vermelho. Se ficar verde, o quality gate n\xe3o\nest\xe1 medindo o que voc\xea acha que ele mede."]}),"\n",(0,s.jsx)(o.h3,{id:"o-caso-2022-verde-com-a-corre\xe7\xe3o-removida",children:"O caso: 20/22 verde com a corre\xe7\xe3o removida"}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsxs)(o.strong,{children:["Fonte: ",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:910-920"}),"."]})}),"\n",(0,s.jsxs)(o.p,{children:["O contexto: ",(0,s.jsx)(o.code,{children:"public/js/game.js:577"})," declara"]}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-js",children:"const vmOffY = (aspect) => VM_OFF[1] * ((16 / 9) / (aspect || 16 / 9));\n"})}),"\n",(0,s.jsxs)(o.p,{children:["\xc9 a corre\xe7\xe3o de enquadramento vertical por aspecto \u2014 o motivo de a arma ficar no mesmo\nlugar em 16:9 e em 3:2 (o dono joga em 3:2). Ela \xe9 ",(0,s.jsx)(o.strong,{children:"chamada"})," no argumento Y de\n",(0,s.jsx)(o.code,{children:"this.vm.root.position.set(...)"}),", em ",(0,s.jsx)(o.code,{children:"public/js/game.js:4873"}),"."]}),"\n",(0,s.jsx)(o.p,{children:"O buraco, medido em 08/2026:"}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["a etapa ",(0,s.jsx)(o.code,{children:"vmOff"})," conferia s\xf3 ",(0,s.jsx)(o.code,{children:"/this\\.vm\\.root\\.position\\.set\\(\\s*VM_OFF\\[0\\]/"})," \u2014 o termo\nX. O termo Y n\xe3o era conferido por ningu\xe9m, e o auditor (",(0,s.jsx)(o.code,{children:"vm-mint-audit.mjs:196"}),",\n",(0,s.jsx)(o.code,{children:"loadOffYFn"}),") l\xea a ",(0,s.jsx)(o.strong,{children:"DECLARA\xc7\xc3O"})," ",(0,s.jsx)(o.code,{children:"const vmOffY = (aspect) => ..."})," por regex ",(0,s.jsx)(o.strong,{children:"sem nunca\nperguntar se algu\xe9m a CHAMA"}),"."]}),"\n",(0,s.jsxs)(o.p,{children:["Resultado: trocando no ",(0,s.jsx)(o.code,{children:"game.js"})," a chamada ",(0,s.jsx)(o.code,{children:"vmOffY(...)"})," por ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," no argumento Y\n\u2014 isto \xe9, ",(0,s.jsx)(o.strong,{children:"removendo por inteiro a corre\xe7\xe3o de enquadramento vertical por aspecto"})," \u2014 o\nquality gate inteiro seguia ",(0,s.jsx)(o.strong,{children:"VERDE (20/22, com VM9, VM10, VM12 e VM15 todas verdes)"}),". Um\nquality gate que n\xe3o distingue o build corrigido do build sem a corre\xe7\xe3o n\xe3o est\xe1 medindo\nnada."]}),"\n"]}),"\n",(0,s.jsxs)(o.p,{children:["Repare no mecanismo do erro, porque ele se repete em qualquer linguagem: ",(0,s.jsxs)(o.strong,{children:["a invariante\nlia a ",(0,s.jsx)(o.em,{children:"declara\xe7\xe3o"})," de uma constante, e n\xe3o o ",(0,s.jsx)(o.em,{children:"uso"}),"."]})," Declarar e n\xe3o chamar \xe9 o jeito mais\nbarato de uma corre\xe7\xe3o sumir com o quality gate verde."]}),"\n",(0,s.jsxs)(o.p,{children:["O conserto foi cir\xfargico e vale copiar. A AUD1 hoje separa os tr\xeas argumentos do\n",(0,s.jsx)(o.code,{children:"position.set(...)"})," com um ",(0,s.jsx)(o.strong,{children:"varredor de par\xeanteses"})," \u2014 n\xe3o ",(0,s.jsx)(o.code,{children:"split(',')"}),", que cortaria\ndentro da chamada de fun\xe7\xe3o \u2014 e exige ",(0,s.jsx)(o.strong,{children:"nominalmente"})," que o argumento Y chame ",(0,s.jsx)(o.code,{children:"vmOffY("}),".\nE fecha o outro caminho junto (",(0,s.jsx)(o.code,{children:"tools/eval/invariants.mjs:1148-1151"}),"): a f\xf3rmula do\n",(0,s.jsx)(o.code,{children:"vmOffY"})," \xe9 ",(0,s.jsxs)(o.strong,{children:["lida do ",(0,s.jsx)(o.code,{children:"game.js"})," e avaliada"]})," em 16/9, e tem que dar exatamente ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"}),"."]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Os dois cheques juntos cobrem os dois jeitos de a corre\xe7\xe3o sumir: ",(0,s.jsx)(o.strong,{children:"apagar a CHAMADA"}),"\n(muta\xe7\xe3o medida em 08/2026) ou ",(0,s.jsx)(o.strong,{children:"adulterar a F\xd3RMULA"}),"."]}),"\n"]}),"\n",(0,s.jsx)(o.h3,{id:"n\xe3o-foi-um-caso-isolado--foram-tr\xeas",children:"N\xe3o foi um caso isolado \u2014 foram tr\xeas"}),"\n",(0,s.jsx)(o.p,{children:"O mesmo buraco apareceu em outros dois lugares, e cada um virou uma etapa nova da AUD1:"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Muta\xe7\xe3o"}),(0,s.jsx)(o.th,{children:"Placar com a corre\xe7\xe3o desfeita"}),(0,s.jsx)(o.th,{children:"Causa do falso verde"}),(0,s.jsx)(o.th,{children:"Onde"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Trocar ",(0,s.jsx)(o.code,{children:"vmOffY(...)"})," por ",(0,s.jsx)(o.code,{children:"VM_OFF[1]"})," no argumento Y"]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.strong,{children:"20/22 verde"})}),(0,s.jsx)(o.td,{children:"a invariante lia a declara\xe7\xe3o, n\xe3o o uso"}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:910-920"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Trocar ",(0,s.jsx)(o.code,{children:"g.rotation.set(pit, yaw, t.roll)"})," por ",(0,s.jsx)(o.code,{children:"g.rotation.set(0, 0, t.roll)"})]}),(0,s.jsx)(o.td,{children:"verde"}),(0,s.jsxs)(o.td,{children:["a tabela ",(0,s.jsx)(o.code,{children:"VM_FRAME.cls"})," continua com os \xe2ngulos, e os tr\xeas espelhos continuam batendo ",(0,s.jsx)(o.strong,{children:"entre si"})]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:932-944"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Apagar ",(0,s.jsx)(o.code,{children:"* (weaponCFG(id).vm ?? 1)"})," da escala do mesh"]}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.strong,{children:"28/37 verde, AUD1 inclusive"}),' ("pior \u0394escala 0.0004")']}),(0,s.jsxs)(o.td,{children:["as duas pontas leem ",(0,s.jsx)(o.code,{children:"vm"})," de ",(0,s.jsx)(o.code,{children:"weapons.js"}),"; ",(0,s.jsxs)(o.strong,{children:["o ",(0,s.jsx)(o.code,{children:"game.js"})," nunca \xe9 perguntado"]})," \u2014 era o auditor conferindo a si mesmo"]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:971-975"})})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsxs)(o.td,{children:["Mutar ",(0,s.jsx)(o.code,{children:"this._adsPose['pistol']"})]}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.strong,{children:"20/22 verde"})}),(0,s.jsx)(o.td,{children:"o ADS n\xe3o tinha invariante nenhuma"}),(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"invariants.mjs:1185"})})]})]})]}),"\n",(0,s.jsx)(o.p,{children:"O padr\xe3o comum das quatro \xe9 o mesmo, e \xe9 o que voc\xea deve procurar na sua invariante:"}),"\n",(0,s.jsx)(o.admonition,{title:"O padr\xe3o do falso verde",type:"danger",children:(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"A r\xe9gua est\xe1 conferindo uma c\xf3pia da regra em vez do jogo."})," Seja porque l\xea a declara\xe7\xe3o\ne n\xe3o o uso, seja porque compara dois espelhos que leem a mesma fonte, seja porque a\ntabela de par\xe2metros continua correta enquanto ningu\xe9m a aplica. Se as duas pontas da sua\ncompara\xe7\xe3o puderem ficar consistentes ",(0,s.jsx)(o.strong,{children:"sem passar pelo c\xf3digo de produ\xe7\xe3o"}),", sua\ninvariante est\xe1 cega."]})}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.code,{children:"tools/eval/mat-check.mjs:18-27"})," resolve isso da forma mais direta poss\xedvel: o corpo do\n",(0,s.jsx)(o.code,{children:"fixVmMaterials"})," \xe9 ",(0,s.jsxs)(o.strong,{children:["recortado do ",(0,s.jsx)(o.code,{children:"game.js"})," e executado"]})," sobre um material-sonda. Se o\nc\xf3digo mudar, a r\xe9gua muda junto. ",(0,s.jsx)(o.em,{children:'"uma r\xe9gua que carrega uma C\xd3PIA da regra mente no dia\nem que a regra muda."'})]}),"\n",(0,s.jsxs)(o.h3,{id:"muta\xe7\xe3o-como-coisa-de-primeira-classe-ui-checkmjs",children:["Muta\xe7\xe3o como coisa de primeira classe: ",(0,s.jsx)(o.code,{children:"ui-check.mjs"})]}),"\n",(0,s.jsxs)(o.p,{children:["O arn\xeas de UI tem uma ",(0,s.jsx)(o.strong,{children:"tabela de muta\xe7\xf5es versionada"}),", e cada uma declara qual quality gate\ntem que ficar vermelho. ",(0,s.jsx)(o.code,{children:"tools/eval/ui-check.mjs:1046-1050"}),":"]}),"\n",(0,s.jsxs)(o.blockquote,{children:["\n",(0,s.jsxs)(o.p,{children:["Cada muta\xe7\xe3o DESFAZ um dos consertos desta rodada (ou fura um quality gate de prop\xf3sito) e diz\nqual quality gate TEM que ficar vermelho. ",(0,s.jsx)(o.strong,{children:"Uma r\xe9gua que n\xe3o reprova a vers\xe3o anterior do\npr\xf3prio arquivo n\xe3o \xe9 r\xe9gua, \xe9 decora\xe7\xe3o."})]}),"\n"]}),"\n",(0,s.jsx)(o.p,{children:"Rodar uma:"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # espera UI1 VERMELHA\nMUT=ui3_prompt_na_mira node tools/eval/ui-check.mjs # espera UI3 VERMELHA\nMUT=ui2_prompt_eterno node tools/eval/ui-check.mjs # espera UI2 VERMELHA\nMUT=ui4_ctf_sem_relogio node tools/eval/ui-check.mjs # espera UI4 VERMELHA\n"})}),"\n",(0,s.jsxs)(o.p,{children:["As 7 muta\xe7\xf5es est\xe3o em ",(0,s.jsx)(o.code,{children:"tools/eval/ui-check.mjs:1051-1134"}),". Duas mec\xe2nicas: ",(0,s.jsx)(o.code,{children:"css"})," reescreve\no ",(0,s.jsx)(o.code,{children:"public/style.css"})," ",(0,s.jsx)(o.strong,{children:"lido em mem\xf3ria"})," (nunca em disco \u2014 outros agentes est\xe3o editando o\narquivo agora), e ",(0,s.jsx)(o.code,{children:"sim"})," monkey-patcha o objeto ",(0,s.jsx)(o.code,{children:"Game"})," j\xe1 bootado. Se a muta\xe7\xe3o ",(0,s.jsx)(o.code,{children:"css"})," n\xe3o\ncasar com nada, o script sai com c\xf3digo 2 dizendo ",(0,s.jsx)(o.em,{children:'"o CSS mudou de forma"'})," \u2014 porque uma\nmuta\xe7\xe3o que n\xe3o aplica tamb\xe9m \xe9 um falso verde (",(0,s.jsx)(o.code,{children:"ui-check.mjs:1164"}),")."]}),"\n",(0,s.jsx)(o.h2,{id:"como-escrever-uma-invariante",children:"Como escrever uma invariante"}),"\n",(0,s.jsx)(o.p,{children:"Checklist, na ordem:"}),"\n",(0,s.jsxs)(o.ol,{children:["\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Comece pela frase do defeito."})," Literal, com as palavras de quem reclamou. Todo\narn\xeas desta base come\xe7a assim, e n\xe3o \xe9 estilo: \xe9 o que impede a invariante de medir\noutra coisa. Ver o cabe\xe7alho de ",(0,s.jsx)(o.code,{children:"tools/eval/map-check.mjs:5-12"})," \u2014 cinco frases do dono,\ncinco invariantes."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Traduza para uma grandeza mensur\xe1vel."}),' "os jogadores est\xe3o SUBMERSOS EMBAIXO DA\nEST\xc1TUA" \u2192 ',(0,s.jsx)(o.em,{children:"existe geometria vis\xedvel do mapa cujo topo passa de 0,30 m acima do ch\xe3o\nlocal naquele ponto"})," (MAP1). Note que a defini\xe7\xe3o operacional inclui ",(0,s.jsx)(o.strong,{children:"por que 0,30 m"}),':\n\xe9 o degrau que o corpo sobe; acima disso n\xe3o \xe9 "passar por cima", \xe9 "estar dentro".']}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Ache a proced\xeancia do teto."})," Arquivo de refer\xeancia + pixel medido + script que\nreproduz. Se n\xe3o existir, ",(0,s.jsx)(o.strong,{children:"diga que \xe9 fallback"})," e cite a fonte publicada, como o C1\nfaz. Nunca invente o n\xfamero."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Me\xe7a o c\xf3digo de produ\xe7\xe3o, n\xe3o uma c\xf3pia dele."})," Importe o m\xf3dulo real, recorte a\nfun\xe7\xe3o do arquivo e execute, ou exija nominalmente a chamada no texto do fonte."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Mute e confirme que fica vermelha."})," Desfa\xe7a a corre\xe7\xe3o que voc\xea acabou de fazer e\nrode o quality gate. Se ficar verde, sua invariante est\xe1 cega \u2014 volte pro passo 4. Se der pra\nautomatizar, registre a muta\xe7\xe3o numa tabela, como o ",(0,s.jsx)(o.code,{children:"ui-check.mjs"})," faz."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Escreva a evid\xeancia, n\xe3o s\xf3 o booleano."})," O quarto argumento do ",(0,s.jsx)(o.code,{children:"put()"})," \xe9 o que\nalgu\xe9m vai ler daqui a tr\xeas meses: ",(0,s.jsx)(o.code,{children:'"0,504 a 0,619 da altura em 52 medidas | 0 fora da faixa"'})," \xe9 \xfatil; ",(0,s.jsx)(o.code,{children:'"ok"'})," n\xe3o \xe9."]}),"\n",(0,s.jsxs)(o.li,{children:[(0,s.jsx)(o.strong,{children:"Escreva o coment\xe1rio de proced\xeancia acima dela."})," Em portugu\xeas, dizendo o que\naconteceu quando o n\xfamero estava errado. \xc9 esse coment\xe1rio que impede a pr\xf3xima rodada\nde refazer o erro."]}),"\n"]}),"\n",(0,s.jsx)(o.h3,{id:"anti-padr\xf5es-que-j\xe1-custaram-caro-aqui",children:"Anti-padr\xf5es que j\xe1 custaram caro aqui"}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Anti-padr\xe3o"}),(0,s.jsx)(o.th,{children:"O que deu"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Ler a declara\xe7\xe3o de uma constante em vez do uso"}),(0,s.jsx)(o.td,{children:"20/22 verde com a corre\xe7\xe3o removida"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Dois espelhos que leem a mesma fonte"}),(0,s.jsx)(o.td,{children:'28/37 verde, "pior \u0394escala 0.0004", com o knob desligado'})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Adaptador de formato quebrado em sil\xeancio"}),(0,s.jsxs)(o.td,{children:["VM1\u2013VM6 ficaram ",(0,s.jsx)(o.strong,{children:"PULADAS desde que o auditor existe"})," \u2014 6 invariantes de viewmodel que nunca rodaram uma vez (",(0,s.jsx)(o.code,{children:"invariants.mjs:121-127"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Medir o v\xe3o contra o ch\xe3o local errado"}),(0,s.jsxs)(o.td,{children:["pickup dentro da piscina reportava v\xe3o ",(0,s.jsx)(o.strong,{children:"0,0000 \u2014 VERDE"})," (",(0,s.jsx)(o.code,{children:"pickup-check.mjs:20-23"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:'"waypoint \u2264 3 m" como proxy de alcance'}),(0,s.jsxs)(o.td,{children:["74 falsos-positivos e verde em bols\xe3o fechado (",(0,s.jsx)(o.code,{children:"pickup-check.mjs:34-42"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Piso sem teto"}),(0,s.jsxs)(o.td,{children:['"boca \u2265 0,66" aceita a boca em 0,95 (arma no por\xe3o) \u2014 foi assim que chegamos a 0,816 (',(0,s.jsx)(o.code,{children:"invariants.mjs:432-434"}),")"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Medir num mundo onde o defeito n\xe3o pode acontecer"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"eval:site"})," cobre ",(0,s.jsx)(o.code,{children:"/ranking"})," e ",(0,s.jsx)(o.strong,{children:"checa corpo"}),", e passou um dia inteiro verde com a p\xe1gina servindo ",(0,s.jsx)(o.strong,{children:"200 com 0 bytes"})," em produ\xe7\xe3o: ele sobe um ",(0,s.jsx)(o.code,{children:"astro dev"})," local, onde ",(0,s.jsx)(o.code,{children:"public/js"})," existe e o ",(0,s.jsx)(o.code,{children:"ENOENT"})," n\xe3o ocorre (BUG-49)"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Aceitar status como prova de p\xe1gina viva"}),(0,s.jsxs)(o.td,{children:["o mesmo BUG-49: ",(0,s.jsx)(o.code,{children:"status === 200"})," chamava de saud\xe1vel uma casca vazia. Corpo agora \xe9 cobrado por ",(0,s.jsx)(o.strong,{children:"tamanho"})]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"N\xfamero medido que ningu\xe9m reprova"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"gl-metrics.mjs"})," media calls/tri\xe2ngulos desde a rodada 3 e nenhuma cl\xe1usula lia o resultado; o teto s\xf3 existia como prosa num coment\xe1rio. o estacionamento da Loja H (",(0,s.jsx)(o.code,{children:"loja_h"}),") chegou a 4.347 calls antes de algu\xe9m olhar"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"Policiar artefato em vez de fonte"}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"mapa-id-check"})," varria ",(0,s.jsx)(o.code,{children:"public/docs/"})," (sa\xedda do Docusaurus) e ficava vermelha quando o bundle publicado estava uma gera\xe7\xe3o atr\xe1s de um rename \u2014 vermelho sem defeito"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:"R\xe9gua que se acusa"}),(0,s.jsx)(o.td,{children:"a mesma: ela precisa citar os ids antigos para cobr\xe1-los, e se varria a si mesma. Nove ocorr\xeancias, todas dela"})]})]})]}),"\n",(0,s.jsx)(o.h2,{id:"esta-p\xe1gina-\xe9-a-doutrina-o-passo-a-passo-\xe9-uma-skill",children:"Esta p\xe1gina \xe9 a doutrina. O passo a passo \xe9 uma skill"}),"\n",(0,s.jsxs)(o.p,{children:["O que fazer, na ordem, quando algu\xe9m reporta um defeito \u2014 reproduzir, medir antes de\nconsertar, refutar o palpite \xf3bvio, mutar a r\xe9gua, rodar o quality gate na ordem certa e reportar\no que ",(0,s.jsx)(o.strong,{children:"n\xe3o"})," foi verificado \u2014 est\xe1 em ",(0,s.jsx)(o.code,{children:".claude/skills/bug-hunt/SKILL.md"}),", com o caso real\nque comprou cada regra. Ela \xe9 escrita para agente ",(0,s.jsx)(o.strong,{children:"e"})," para gente, e aponta de volta para\nesta p\xe1gina em vez de repeti-la."]}),"\n",(0,s.jsxs)(o.h2,{id:"port\xf5es-que-n\xe3o-cabem-no-check-e-por-qu\xea",children:["Port\xf5es que N\xc3O cabem no ",(0,s.jsx)(o.code,{children:"check"}),", e por qu\xea"]}),"\n",(0,s.jsxs)(o.p,{children:["Tr\xeas r\xe9guas exigem insumo que o port\xe3o r\xe1pido n\xe3o tem \u2014 navegador, ou o build pronto. Elas\nficam de fora de prop\xf3sito e s\xe3o passo de pr\xe9-deploy, junto do ",(0,s.jsx)(o.code,{children:"eval:boot"}),". Cada uma nasceu\nde um defeito que os port\xf5es existentes n\xe3o podiam ver:"]}),"\n",(0,s.jsxs)(o.table,{children:[(0,s.jsx)(o.thead,{children:(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.th,{children:"Comando"}),(0,s.jsx)(o.th,{children:"O que mede"}),(0,s.jsx)(o.th,{children:"O buraco que fechou"})]})}),(0,s.jsxs)(o.tbody,{children:[(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:ssr"})}),(0,s.jsxs)(o.td,{children:["toda p\xe1gina ",(0,s.jsx)(o.code,{children:"prerender = false"})," entrega ",(0,s.jsx)(o.strong,{children:"corpo"}),", medido no artefato do build, entrando no diret\xf3rio da fun\xe7\xe3o (o cwd de produ\xe7\xe3o)"]}),(0,s.jsxs)(o.td,{children:[(0,s.jsx)(o.code,{children:"/mapa"}),", ",(0,s.jsx)(o.code,{children:"/ranking"})," e ",(0,s.jsx)(o.code,{children:"/u/*"})," serviram ",(0,s.jsx)(o.strong,{children:"200 com 0 bytes"})," por um dia. O ",(0,s.jsx)(o.code,{children:"eval:site"})," mede um ",(0,s.jsx)(o.code,{children:"astro dev"})," local, onde o defeito n\xe3o pode acontecer"]})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:cena"})}),(0,s.jsxs)(o.td,{children:["teto de draw calls e tri\xe2ngulos por frame, ",(0,s.jsx)(o.strong,{children:"por mapa"})]}),(0,s.jsx)(o.td,{children:"o n\xfamero era medido desde a rodada 3 e ningu\xe9m reprovava. Descobriu que o mapa da Quebrada custa 1.8 k calls e roda a metade do fps dos outros"})]}),(0,s.jsxs)(o.tr,{children:[(0,s.jsx)(o.td,{children:(0,s.jsx)(o.code,{children:"npm run eval:mapid"})}),(0,s.jsx)(o.td,{children:"nenhum id no estilo Counter-Strike sobrevive, todo id antigo resolve, e toda pr\xe9via existe em disco"}),(0,s.jsxs)(o.td,{children:["renomear id sem renomear a imagem deixa cartaz quebrado no menu: 404 no navegador, ",(0,s.jsx)(o.strong,{children:"nada"})," no build"]})]})]})]}),"\n",(0,s.jsxs)(o.p,{children:["O teto do ",(0,s.jsx)(o.code,{children:"eval:cena"})," mora em ",(0,s.jsx)(o.code,{children:"tools/eval/cena-tetos.mjs"}),", importado tanto pela r\xe9gua de\nnavegador quanto pelas cl\xe1usulas ",(0,s.jsx)(o.code,{children:"CENA"})," do ",(0,s.jsx)(o.code,{children:"invariants.mjs"})," \u2014 um limiar, dois leitores. Dois\nn\xfameros para o mesmo conceito \xe9 o instrumento discordando de si, e isso j\xe1 custou uma rodada\ninteira aqui."]}),"\n",(0,s.jsx)(o.h2,{id:"rodar-o-quality-gate",children:"Rodar o quality gate"}),"\n",(0,s.jsx)(o.pre,{children:(0,s.jsx)(o.code,{className:"language-bash",children:"node tools/eval/invariants.mjs # tudo que roda sem browser\nnode tools/eval/invariants.mjs --json # sa\xedda pra m\xe1quina\nnpm run check # syntax + quality gate + vm + coice + bots\nnpm run check:fast # segundos \u2014 rode este primeiro, sempre\n\n# pr\xe9-deploy: exigem navegador ou build, e por isso ficam fora dos de cima\nnpm run eval:boot # o jogo ABRE?\nnpm run build && npm run eval:ssr # p\xe1gina SSR entrega corpo?\nnpm run eval:cena # custo de cena dentro do teto?\n"})}),"\n",(0,s.jsxs)(o.p,{children:["Fontes atuais de produ\xe7\xe3o, dados e d\xedvida conhecida: ",(0,s.jsx)(o.a,{href:"/docs/estado",children:"Estado atual"}),"."]})]})}function m(e={}){const{wrapper:o}={...(0,n.R)(),...e.components};return o?(0,s.jsx)(o,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453(e,o,a){a.d(o,{R:()=>i,x:()=>d});var r=a(6540);const s={},n=r.createContext(s);function i(e){const o=r.useContext(n);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function d(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),r.createElement(n.Provider,{value:o},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/assets/js/runtime~main.c8068ae7.js b/public/docs/assets/js/runtime~main.30b9a464.js
similarity index 84%
rename from public/docs/assets/js/runtime~main.c8068ae7.js
rename to public/docs/assets/js/runtime~main.30b9a464.js
index 6ea7018f2..0e90a3acf 100644
--- a/public/docs/assets/js/runtime~main.c8068ae7.js
+++ b/public/docs/assets/js/runtime~main.30b9a464.js
@@ -1 +1 @@
-(()=>{"use strict";var e={};const t={};function r(o){const n=t[o];if(void 0!==n)return n.exports;const a=t[o]={exports:{}};return e[o].call(a.exports,a,a.exports,r),a.exports}r.m=e,(()=>{const e=[];r.O=(t,o,n,a)=>{if(o){a=a||0;for(var c=e.length;c>0&&e[c-1][2]>a;c--)e[c]=e[c-1];return void(e[c]=[o,n,a])}let s=1/0;for(c=0;c{const t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;r.t=function(o,n){if(1&n&&(o=this(o)),8&n)return o;if("object"==typeof o&&o){if(4&n&&o.__esModule)return o;if(16&n&&"function"==typeof o.then)return o}const a=Object.create(null);r.r(a);const c={};t=t||[null,e({}),e([]),e(e)];for(var s=2&n&&o;("object"==typeof s||"function"==typeof s)&&!~t.indexOf(s);s=e(s))Object.getOwnPropertyNames(s).forEach(e=>c[e]=()=>o[e]);return c.default=()=>o,r.d(a,c),a}})(),r.d=(e,t)=>{if(Array.isArray(t))for(var o=0;o{const t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;r.t=function(o,n){if(1&n&&(o=this(o)),8&n)return o;if("object"==typeof o&&o){if(4&n&&o.__esModule)return o;if(16&n&&"function"==typeof o.then)return o}const a=Object.create(null);r.r(a);const c={};t=t||[null,e({}),e([]),e(e)];for(var s=2&n&&o;("object"==typeof s||"function"==typeof s)&&!~t.indexOf(s);s=e(s))Object.getOwnPropertyNames(s).forEach(e=>c[e]=()=>o[e]);return c.default=()=>o,r.d(a,c),a}})(),r.d=(e,t)=>{if(Array.isArray(t))for(var o=0;ogit shortlog -sn --no-merges descontando os autores que são agentes de IA (que assinam como Claude /
Claude (gauntlet …)).ruben-cytonic, Emerson Garrido, Ruben, rubenmarcus, Ruben Marcus, William Oliveira, Juan Versolato Lopes, daeeseD, matheusgb, Maná Soares, daltonfontes. O resto dos commits é assinado por agentes de IA. Branch não é repositório: quem contribuiu num ramo que esta branch não contém não aparece aqui.ruben-cytonic, Ruben, Emerson Garrido, rubenmarcus, Ruben Marcus, William Oliveira, Juan Versolato Lopes, daeeseD, matheusgb, Maná Soares, daltonfontes. O resto dos commits é assinado por agentes de IA. Branch não é repositório: quem contribuiu num ramo que esta branch não contém não aparece aqui.
diff --git a/public/docs/en/404.html b/public/docs/en/404.html
index 9eab0b53a..ca52eba08 100644
--- a/public/docs/en/404.html
+++ b/public/docs/en/404.html
@@ -4,7 +4,7 @@
node tools/gen-docs.mjs. Fonte: git shortlog -sn --no-merges (descontando autores que são agentes)The indexed fi
gen-arch.mjs indexes — a generated block, regenerated by
npm run docs and checked by npm run docs:check:
-File Lines public/js/game.js6,838 public/js/main.js2,646 public/js/characters.js1,068 public/js/glbchars.js837 public/js/vmattach.js628 public/js/weapons.js344 public/js/springs.js260 public/js/: 31,744 lines in 44 files. The symbol-to-line index lives in tools/eval/ARCH.md.
+File Lines public/js/game.js6,910 public/js/main.js2,698 public/js/characters.js1,068 public/js/glbchars.js844 public/js/vmattach.js628 public/js/weapons.js346 public/js/springs.js260 public/js/: 32,001 lines in 44 files. The symbol-to-line index lives in tools/eval/ARCH.md.
diff --git a/public/docs/en/assets/js/7cf461fa.6b3dd629.js b/public/docs/en/assets/js/7cf461fa.1cb4b3cb.js
similarity index 98%
rename from public/docs/en/assets/js/7cf461fa.6b3dd629.js
rename to public/docs/en/assets/js/7cf461fa.1cb4b3cb.js
index 1277ae0ea..c75d7d361 100644
--- a/public/docs/en/assets/js/7cf461fa.6b3dd629.js
+++ b/public/docs/en/assets/js/7cf461fa.1cb4b3cb.js
@@ -1 +1 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[3],{5805(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>d,default:()=>a,frontMatter:()=>o,metadata:()=>t,toc:()=>h});const t=JSON.parse('{"id":"arquitetura","title":"Architecture: N agents in the same file","description":"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision.","source":"@site/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md","sourceDirName":".","slug":"/architecture","permalink":"/docs/en/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/arquitetura.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"id":"arquitetura","title":"Architecture: N agents in the same file","sidebar_label":"Architecture","sidebar_position":5,"slug":"/architecture","description":"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision."},"sidebar":"dev","previous":{"title":"BotBrain","permalink":"/docs/en/botbrain"},"next":{"title":"How to contribute","permalink":"/docs/en/contributing"}}');var i=s(4848),r=s(8453);const o={id:"arquitetura",title:"Architecture: N agents in the same file",sidebar_label:"Architecture",sidebar_position:5,slug:"/architecture",description:"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision."},d="Architecture: N agents in the same file",c={},h=[{value:"Why this document is generated by a script",id:"why-generated-by-script",level:2},{value:"The indexed files",id:"indexed-files",level:2},{value:"The largest methods in node tools/gen-docs.mjs. Source: wc -l public/js/*.jsgame.js \u2014 where the conflict lives",id:"largest-methods",level:3},{value:"Disjoint line ranges",id:"disjoint-line-ranges",level:2},{value:"How it works",id:"how-it-works",level:3},{value:"The conflict table",id:"conflict-table",level:3},{value:"The red zones",id:"red-zones",level:3},{value:"The operating rules",id:"operating-rules",level:3},{value:"The three zones of the repository",id:"three-zones",level:2},{value:"Practical consequence",id:"practical-consequence",level:3},{value:"Content data system",id:"content-data-system",level:2},{value:"What is generated, and what is not",id:"generated-vs-not",level:2},{value:"What the generator does NOT solve: arquivo:linha pointers in prose",id:"what-the-generator-does-not-solve",level:3}];function l(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:["\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"architecture-n-agents-in-the-same-file",children:"Architecture: N agents in the same file"})}),"\n",(0,i.jsx)(n.h2,{id:"why-generated-by-script",children:"Why this document is generated by a script"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," is not hand-written. It is generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-arch.mjs"}),",\nand the reason is in the script's header (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:5-8"}),"):"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The hand-written ",(0,i.jsx)(n.code,{children:"ARCH.md"}),' said "game.js (3234 lines)" when the file had 5361.\nEvery ',(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointer in the conflict table was off \u2014 and that\ntable is precisely what keeps two agents (or two contributors) from editing the\nsame region. A hand-written line-number index goes stale on the first\ncommit; the only fix is to generate it."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the separation that makes this work (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:11-13"}),", quoted verbatim \u2014\nthe source comments are in Portuguese):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"frente -> S\xcdMBOLO = conhecimento humano, est\xe1vel, vive nas FRENTES do script\ns\xedmbolo -> LINHA = vol\xe1til, \xe9 o que este script resolve toda vez\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:'Translation: "front \u2192 SYMBOL = human knowledge, stable, lives in the script\'s FRENTES;\nsymbol \u2192 LINE = volatile, it is what this script resolves every time."'})}),"\n",(0,i.jsxs)(n.p,{children:["The old ",(0,i.jsx)(n.code,{children:"ARCH.md"})," pinned ",(0,i.jsx)(n.strong,{children:"front \u2192 line"}),", mixing the two shelf lives. It is\na small idea with a big consequence: the work partition is declared in terms\nthat do not change (method names), and the resolution to volatile coordinates (line\nnumbers) is recomputed on every run."]}),"\n",(0,i.jsxs)(n.admonition,{type:"note",children:[(0,i.jsxs)(n.mdxAdmonitionTitle,{children:["The ",(0,i.jsx)(n.code,{children:"arch:check"})," is RED right now \u2014 and that is the best demonstration on this page"]}),(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"npm run arch"})," and ",(0,i.jsx)(n.code,{children:"npm run arch:check"})," exist today in the root ",(0,i.jsx)(n.code,{children:"package.json"}),", and the check\nis not passing. Gate output below, quoted verbatim \u2014 the tools print in Portuguese:"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"$ npm run arch:check\n\u2717 ARCH1 ARCH.md est\xe1 DESATUALIZADO em rela\xe7\xe3o ao c\xf3digo.\n game.js tem 6428 linhas; o \xedndice do ARCH.md n\xe3o bate.\n Rode: npm run arch\n"})}),(0,i.jsxs)(n.p,{children:["The message misleads on purpose: it ",(0,i.jsx)(n.strong,{children:"talks about lines because that is the summary it\nknows how to print"}),", but what ",(0,i.jsx)(n.code,{children:"--check"})," compares is the entire generated block, byte by byte \u2014 and that\nblock also carries the game's version number. Correct symbol index plus stale version gives\nthe same red. One command fixes it."]}),(0,i.jsxs)(n.p,{children:["A caveat that still holds: in CI the step has ",(0,i.jsx)(n.code,{children:"continue-on-error: true"}),", so the\ncheck runs but ",(0,i.jsx)(n.strong,{children:"does not block"})," \u2014 which is exactly how it managed to stay\nred without anyone noticing. Removing that line is what turns it into a real\ngate."]})]}),"\n",(0,i.jsx)(n.h2,{id:"indexed-files",children:"The indexed files"}),"\n",(0,i.jsxs)(n.p,{children:["Size of the files ",(0,i.jsx)(n.code,{children:"gen-arch.mjs"})," indexes \u2014 a generated block, regenerated by\n",(0,i.jsx)(n.code,{children:"npm run docs"})," and checked by ",(0,i.jsx)(n.code,{children:"npm run docs:check"}),":"]}),"\n","\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"File"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Lines"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/game.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"6,838"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/main.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"2,646"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/characters.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"1,068"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/glbchars.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"837"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/vmattach.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"628"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/weapons.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"344"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/springs.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"260"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:["Total in ",(0,i.jsx)(n.code,{children:"public/js/"}),": ",(0,i.jsx)(n.strong,{children:"31,744 lines in 44 files"}),". The symbol-to-line index lives in ",(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"wc -l public/js/*.js"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.h3,{id:"largest-methods",children:["The largest methods in ",(0,i.jsx)(n.code,{children:"game.js"})," \u2014 where the conflict lives"]}),"\n",(0,i.jsxs)(n.p,{children:["This table is ",(0,i.jsx)(n.strong,{children:"not reproduced here"}),", and the reason is this page's own thesis: it is\n",(0,i.jsx)(n.code,{children:"linha \u2192 m\xe9todo"}),", the volatile side of the separation, and duplicating it in a prose page creates a\nsecond copy that ages on its own. It lives generated, in one place only:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"npm run arch # regenerates tools/eval/ARCH.md\nnode tools/gen-arch.mjs --json # the raw index, for another tool\n"})}),"\n",(0,i.jsxs)(n.p,{children:["What does ",(0,i.jsx)(n.strong,{children:"not"})," age, and is therefore written here: ",(0,i.jsx)(n.code,{children:"_updateBot()"})," is by far the largest\nmethod in the file and is flagged by the index itself as an ",(0,i.jsx)(n.strong,{children:"extraction candidate"}),";\n",(0,i.jsx)(n.code,{children:"constructor()"}),", ",(0,i.jsx)(n.code,{children:"update()"})," and ",(0,i.jsx)(n.code,{children:"_dom()"})," are ",(0,i.jsx)(n.strong,{children:"red zone, append-only"}),", because any\nfront may need them. Big method = unreviewable PR and conflicting merge \u2014 extracting\n",(0,i.jsx)(n.code,{children:"_updateBot"})," is high-value, medium-risk work, and it requires coordinating first, because the\nregion is contested."]}),"\n",(0,i.jsx)(n.h2,{id:"disjoint-line-ranges",children:"Disjoint line ranges"}),"\n",(0,i.jsxs)(n.p,{children:["This is the mechanism that lets several agents (or contributors) edit the ",(0,i.jsx)(n.strong,{children:"same\nfile"})," \u2014 the largest in the repository, thousands of lines \u2014 at the same time, without merge\nconflict."]}),"\n",(0,i.jsx)(n.h3,{id:"how-it-works",children:"How it works"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Each front declares SYMBOLS, never lines."})," In ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:32-73"}),", the\n",(0,i.jsx)(n.code,{children:"FRENTES"})," constant lists, per front, three things: exclusive ",(0,i.jsx)(n.code,{children:"arquivos"}),", ",(0,i.jsx)(n.code,{children:"simbolos"}),"\n(methods) and ",(0,i.jsx)(n.code,{children:"consts"})," (top-level constants). For example, the ARMAS/VIEWMODEL front owns\n",(0,i.jsx)(n.code,{children:"_buildViewModels"}),", ",(0,i.jsx)(n.code,{children:"_vmFrame"}),", ",(0,i.jsx)(n.code,{children:"_tryShoot"}),", ",(0,i.jsx)(n.code,{children:"_shotRecoil"}),"\u2026 and the constants ",(0,i.jsx)(n.code,{children:"WEAPONS"}),",\n",(0,i.jsx)(n.code,{children:"VM_FOV_DEFAULT"}),", ",(0,i.jsx)(n.code,{children:"VM_OFF"}),", ",(0,i.jsx)(n.code,{children:"REC_DEG"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The script indexes the file and resolves symbol \u2192 range."})," ",(0,i.jsx)(n.code,{children:"indexar()"}),"\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:80-111"}),") scans the file line by line with three patterns: class\nmethod (exactly 2 spaces of indentation), ",(0,i.jsx)(n.strong,{children:"arrow method assigned at runtime"}),"\n(",(0,i.jsx)(n.code,{children:"this._vmFrame = (force) => {"}),") and top-level declaration. Each symbol ends where the\nnext one begins."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Contiguous ranges are merged"})," (gap \u2264 12 lines) to keep the table readable\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:172-178"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Overlap between fronts is detected"}),", because a conflict table that contradicts\nitself is worse than none (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:190-200"}),")."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["The detail in step 2 deserves highlighting: v1 of the script only saw class methods, so\n",(0,i.jsx)(n.code,{children:"_vmFrame"})," \u2014 about 100 lines born ",(0,i.jsx)(n.strong,{children:"inside"})," another method, as an arrow that\ncloses over local variables \u2014 was ",(0,i.jsx)(n.strong,{children:"invisible in the index"}),"\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:95-97"}),"). An index that cannot see the most contested method in the file is\nworse than no index at all, because it gives false confidence."]}),"\n",(0,i.jsx)(n.h3,{id:"conflict-table",children:"The conflict table"}),"\n",(0,i.jsxs)(n.p,{children:["From ",(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," (generated block \u2014 the ranges below are from the previous generation; run\n",(0,i.jsx)(n.code,{children:"node tools/gen-arch.mjs"})," for today's):"]}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Front"}),(0,i.jsx)(n.th,{children:"Exclusive files"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"ARMAS / VIEWMODEL"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"vmattach.js"})," ",(0,i.jsx)(n.code,{children:"springs.js"})," ",(0,i.jsx)(n.code,{children:"weapons.js"})," ",(0,i.jsx)(n.code,{children:"fparms.js"})," ",(0,i.jsx)(n.code,{children:"handik.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"BOTS / JOGABILIDADE"})}),(0,i.jsxs)(n.td,{children:["\u2014 (ranges in ",(0,i.jsx)(n.code,{children:"game.js"})," only)"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"MAPAS / MUNDO"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"maps.js"})," ",(0,i.jsx)(n.code,{children:"mapprops.js"})," ",(0,i.jsx)(n.code,{children:"map_brasilia.js"})," ",(0,i.jsx)(n.code,{children:"map_havan.js"})," ",(0,i.jsx)(n.code,{children:"map_piscina.js"})," ",(0,i.jsx)(n.code,{children:"map_piscinao_ramos.js"})," ",(0,i.jsx)(n.code,{children:"map_ferrovelho.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"GR\xc1FICOS / FX"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"bloom.js"})," ",(0,i.jsx)(n.code,{children:"textures.js"})," ",(0,i.jsx)(n.code,{children:"vao.js"})," ",(0,i.jsx)(n.code,{children:"stylize.js"})," ",(0,i.jsx)(n.code,{children:"gpuparticles.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"UI / HUD / MENU"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"main.js"})," ",(0,i.jsx)(n.code,{children:"public/style.css"})," ",(0,i.jsx)(n.code,{children:"src/pages/index.astro"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"\xc1UDIO"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"audio.js"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"PERSONAGENS"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"characters.js"})," ",(0,i.jsx)(n.code,{children:"glbchars.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"SITE / BACKEND"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"src/"})})]})]})]}),"\n",(0,i.jsxs)(n.admonition,{title:"Two map files have NO declared owner",type:"caution",children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"map_quebrada.js"})," (1.319 lines, the newest map) and ",(0,i.jsx)(n.code,{children:"map_decals.js"})," ",(0,i.jsx)(n.strong,{children:"appear in no\nfront at all"})," in ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})," \u2014 the list above is a faithful copy of ",(0,i.jsx)(n.code,{children:"FRENTES"}),", and they\nare not there. Whoever edits those two collides with nobody ",(0,i.jsx)(n.em,{children:"according to the table"}),", which is\nprecisely the guarantee the table is supposed to give and does not. Adding them is one line in\n",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})," followed by ",(0,i.jsx)(n.code,{children:"npm run arch"}),"."]}),(0,i.jsxs)(n.p,{children:["(",(0,i.jsx)(n.code,{children:"map.js"})," was once listed here and ",(0,i.jsx)(n.strong,{children:"no longer exists"}),': it was the "Pra\xe7a (cl\xe1ssico)", deleted\nalong with the ',(0,i.jsx)(n.code,{children:"praca_old"})," map.)"]})]}),"\n",(0,i.jsx)(n.h3,{id:"red-zones",children:"The red zones"}),"\n",(0,i.jsxs)(n.p,{children:["Three methods are ",(0,i.jsx)(n.strong,{children:"append-only"}),", because any front may need them\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:75-77"}),"):"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"update()"})," \u2014 the loop"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_dom()"})," \u2014 the HUD wiring"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"constructor()"})," \u2014 one of the largest methods in the file (today's size is in ",(0,i.jsx)(n.code,{children:"ARCH.md"}),")"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Editing the middle of these is the fastest way for two contributors to trample each other.\nAppend at the end; do not reorganize."}),"\n",(0,i.jsx)(n.h3,{id:"operating-rules",children:"The operating rules"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Declare your front before editing."})," If it is a human PR, say so in the description."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsxs)(n.strong,{children:["In ",(0,i.jsx)(n.code,{children:"game.js"}),", edit by chunk \u2014 never overwrite the whole file."]})," A\ntool that rewrites the file erases the work of whoever is on the other range."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Two fronts with disjoint ranges run in parallel."})," The generated ",(0,i.jsx)(n.code,{children:"ARCH.md"})," records\nthat this was measured: ",(0,i.jsx)(n.em,{children:'"3 agents edited disjoint ranges simultaneously with zero\ncontent conflict"'})," (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:163"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Touched a symbol? Move the name in the front's declaration, not the number."})," The script\nwarns when a declared symbol disappears from the code."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"Why this matters to you, human",type:"tip",children:(0,i.jsxs)(n.p,{children:["The same partition that prevents collisions between agents is what makes a PR of yours reviewable. A\nPR that touches ",(0,i.jsx)(n.code,{children:"_updateBot"})," + ",(0,i.jsx)(n.code,{children:"style.css"})," + ",(0,i.jsx)(n.code,{children:"map_havan.js"})," is three PRs hidden in one, and\nwill collide with three different fronts. One PR per front lands fast."]})}),"\n",(0,i.jsx)(n.h2,{id:"three-zones",children:"The three zones of the repository"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"public/ game vanilla ES modules, zero build, vendored Three.js\nsrc/ site Astro + Vercel adapter, SSR API routes\ntools/ harness .mjs/.py scripts \u2014 the ruler, the gate and the probes\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Versions, counts and what each tool does are in\n",(0,i.jsx)(n.a,{href:"/docs/en/stack",children:"Stack and tools"})," \u2014 ",(0,i.jsx)(n.strong,{children:"generated"}),", not hand-written."]}),"\n",(0,i.jsx)(n.p,{children:"The coupling between them is deliberately thin and worth understanding:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The site loads the game via import map"}),", in ",(0,i.jsx)(n.code,{children:"src/pages/index.astro:97-123"}),". It is the only\nplace where Astro knows the game's modules exist."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The harness loads the game straight from disk"}),", with no browser: ",(0,i.jsx)(n.code,{children:"tools/eval/harness.mjs"})," stubs\nDOM/canvas/",(0,i.jsx)(n.code,{children:"fetch"})," and imports ",(0,i.jsx)(n.code,{children:"public/js/game.js"})," as a module. That is why the gate measures\nproduction code, not a reimplementation."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"tools/eval/serve.mjs:15"})})," bridges to the test case: it serves ",(0,i.jsx)(n.code,{children:"public/"})," and maps\n",(0,i.jsx)(n.code,{children:"/"})," to the ",(0,i.jsx)(n.code,{children:"index.astro"})," source, with no Astro in the path."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"practical-consequence",children:"Practical consequence"}),"\n",(0,i.jsxs)(n.p,{children:["The game ",(0,i.jsx)(n.strong,{children:"cannot"})," gain a runtime dependency or a build step. This is not\nconservatism: it is what lets ",(0,i.jsx)(n.code,{children:"harness.mjs"})," boot the ",(0,i.jsx)(n.code,{children:"Game"})," class in pure node\nin seconds, which is what makes the gate exist. A bundler in the middle would break the ruler (quality gate) along\nwith the portability."]}),"\n",(0,i.jsx)(n.h2,{id:"content-data-system",children:"Content data system"}),"\n",(0,i.jsxs)(n.p,{children:["Today maps, weapons and characters are ",(0,i.jsx)(n.strong,{children:"code"}),": each ",(0,i.jsx)(n.code,{children:"map_*.js"}),' is geometry declared by\nhand, and the largest of them rival the system modules in size. The\n"content as data" direction in\n',(0,i.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,i.jsx)(n.code,{children:"docs/ROADMAP.md"})}),"\nwants to migrate this to JSON with a single loader, so that a content contribution becomes\n",(0,i.jsx)(n.em,{children:'"open a JSON and create content"'})," instead of ",(0,i.jsx)(n.em,{children:'"a risky hand-coded code PR"'}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["If you want the highest-leverage work in the entire project, this is it. See\n",(0,i.jsx)(n.a,{href:"./status",children:"Current state"}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"generated-vs-not",children:"What is generated, and what is not"}),"\n",(0,i.jsx)(n.p,{children:"Two things in this repository are generated by script, and for the same reason:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Generated"}),(0,i.jsx)(n.th,{children:"Script"}),(0,i.jsx)(n.th,{children:"Gate"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," \u2014 symbol\u2192line index and conflict table"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"npm run arch:check"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["The numeric blocks of ",(0,i.jsx)(n.code,{children:"README.md"})," and of this documentation"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"tools/gen-docs.mjs"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"npm run docs:check"})," (in ",(0,i.jsx)(n.code,{children:"check:fast"}),")"]})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"The rule that separates what goes in and what stays out:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Derivable from the code?"})," It becomes a generated block, between markers, with ",(0,i.jsx)(n.code,{children:"--check"})," in the gate.\nCounts of lines, of characters, of weapons, of maps, of scripts, of invariants,\nversion, the ",(0,i.jsx)(n.code,{children:"package.json"})," script list, dependency version."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Not derivable?"})," Then it is a decision or an explanation \u2014 and it ",(0,i.jsx)(n.strong,{children:"must not contain a number that\nages"}),". Write it without the number, or cite the command that produces it. The gate's scoreboard,\nfor example, depends on which inputs exist on the machine: it lives pasted from a real\nrun in ",(0,i.jsx)(n.code,{children:"KNOWN-BUGS.md"}),", not repeated across five pages."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the reason ",(0,i.jsx)(n.code,{children:"--check"})," is in the gate, not merely available: ",(0,i.jsx)(n.strong,{children:"what does not become a ruler is\noptimized away."})," A generator nobody is forced to run goes stale in a week,\nand then the documentation is back to lying with the appearance of rigor \u2014 which is worse than lying without\nit."]}),"\n",(0,i.jsxs)(n.admonition,{title:"Where you put the new gate in the chain matters",type:"danger",children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"check:fast"})," is a chain of ",(0,i.jsx)(n.code,{children:"&&"}),": the first error cuts off the rest. ",(0,i.jsx)(n.code,{children:"arch:check"})," has been\nred for days, so ",(0,i.jsx)(n.strong,{children:"every gate placed after it is born dead"})," \u2014 it runs zero times\nand nobody notices, because the output stops earlier. That is exactly what happened to the first\nversion of ",(0,i.jsx)(n.code,{children:"docs:check"}),", and it is the same failure mode as BUG-02 (the gate measuring the viewmodel\nfrom yesterday because the ",(0,i.jsx)(n.code,{children:"&&"})," cut off before the JSON was regenerated)."]}),(0,i.jsxs)(n.p,{children:["That is why ",(0,i.jsx)(n.code,{children:"docs:check"})," comes ",(0,i.jsx)(n.strong,{children:"before"})," ",(0,i.jsx)(n.code,{children:"arch:check"})," in ",(0,i.jsx)(n.code,{children:"package.json"}),", with the reason\nwritten in the ",(0,i.jsx)(n.code,{children:"//check:fast"})," key. When ",(0,i.jsx)(n.code,{children:"ARCH.md"})," is regenerated and ",(0,i.jsx)(n.code,{children:"arch:check"})," goes\ngreen again, the order stops mattering; until then, it matters."]})]}),"\n",(0,i.jsxs)(n.p,{children:["Pasting a new block is writing the marker and running ",(0,i.jsx)(n.code,{children:"npm run docs"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"{/* BEGIN:GERADO:BLOCK_NAME \u2014 n\xe3o edite \xe0 m\xe3o, rode `npm run docs` */}\n{/* END:GERADO:BLOCK_NAME */}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["(",(0,i.jsx)(n.code,{children:"BLOCK_NAME"})," is one of the keys of the ",(0,i.jsx)(n.code,{children:"BLOCOS"})," object at the top of ",(0,i.jsx)(n.code,{children:"gen-docs.mjs"}),". A\ndeclared block that nobody consumes becomes a loud warning in the output \u2014 an orphan block is dead code that\npretends to be documentation.)"]}),"\n",(0,i.jsxs)(n.p,{children:["In plain Markdown (",(0,i.jsx)(n.code,{children:"README.md"}),") the marker is an HTML comment (",(0,i.jsx)(n.code,{children:"\x3c!-- BEGIN:GERADO:\u2026 --\x3e"}),").\nIn the pages of this doc it is an ",(0,i.jsx)(n.strong,{children:"MDX"})," comment (",(0,i.jsx)(n.code,{children:"{/* \u2026 */}"}),"): Docusaurus 3 compiles ",(0,i.jsx)(n.code,{children:".md"}),"\nas MDX, and an HTML comment there is a parse error that takes down the build. The generator accepts both\nsyntaxes and preserves whichever it finds."]}),"\n",(0,i.jsxs)(n.h3,{id:"what-the-generator-does-not-solve",children:["What the generator does NOT solve: ",(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointers in prose"]}),"\n",(0,i.jsxs)(n.p,{children:["A ",(0,i.jsx)(n.code,{children:"game.js:5361"})," written in the middle of a paragraph is the cheap version of the same defect \u2014 it\npoints to the wrong place at the first commit that touches the file. It cannot be generated (the\npointer is part of the sentence), but the ",(0,i.jsx)(n.strong,{children:"gross case can be detected"}),": a pointer that points\npast the end of the file."]}),"\n","\n",(0,i.jsxs)(n.p,{children:["No ",(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointer in the docs points outside the file it cites. \u2713"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["This checks only the file's ",(0,i.jsx)(n.strong,{children:"bound"}),": a pointer that still fits but changed subject passes here. That is why the house doctrine is to declare the SYMBOL and leave the line to the generator \u2014 see ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"sweep of "}),"arquivo",":linha",(0,i.jsx)(n.code,{children:" across README/STATUS/HANDOFF/KNOWN-BUGS/docs/docs/SKILL"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.p,{children:["That is why the doctrine is to declare the ",(0,i.jsx)(n.strong,{children:"symbol"})," and leave the line to the generator. When the\n",(0,i.jsx)(n.code,{children:"arquivo:linha"})," really is necessary, cite alongside it the name of what lives there \u2014 that way whoever reads it\na month from now finds it via ",(0,i.jsx)(n.code,{children:"grep"})," even with the pointer shifted."]})]})}function a(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453(e,n,s){s.d(n,{R:()=>o,x:()=>d});var t=s(6540);const i={},r=t.createContext(i);function o(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
+"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[3],{5805(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>d,default:()=>a,frontMatter:()=>o,metadata:()=>t,toc:()=>h});const t=JSON.parse('{"id":"arquitetura","title":"Architecture: N agents in the same file","description":"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision.","source":"@site/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md","sourceDirName":".","slug":"/architecture","permalink":"/docs/en/architecture","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/arquitetura.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"id":"arquitetura","title":"Architecture: N agents in the same file","sidebar_label":"Architecture","sidebar_position":5,"slug":"/architecture","description":"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision."},"sidebar":"dev","previous":{"title":"BotBrain","permalink":"/docs/en/botbrain"},"next":{"title":"How to contribute","permalink":"/docs/en/contributing"}}');var i=s(4848),r=s(8453);const o={id:"arquitetura",title:"Architecture: N agents in the same file",sidebar_label:"Architecture",sidebar_position:5,slug:"/architecture",description:"The real architecture, generated by tools/gen-arch.mjs \u2014 and the disjoint line-range mechanism that lets agents work in parallel without collision."},d="Architecture: N agents in the same file",c={},h=[{value:"Why this document is generated by a script",id:"why-generated-by-script",level:2},{value:"The indexed files",id:"indexed-files",level:2},{value:"The largest methods in game.js \u2014 where the conflict lives",id:"largest-methods",level:3},{value:"Disjoint line ranges",id:"disjoint-line-ranges",level:2},{value:"How it works",id:"how-it-works",level:3},{value:"The conflict table",id:"conflict-table",level:3},{value:"The red zones",id:"red-zones",level:3},{value:"The operating rules",id:"operating-rules",level:3},{value:"The three zones of the repository",id:"three-zones",level:2},{value:"Practical consequence",id:"practical-consequence",level:3},{value:"Content data system",id:"content-data-system",level:2},{value:"What is generated, and what is not",id:"generated-vs-not",level:2},{value:"What the generator does NOT solve: arquivo:linha pointers in prose",id:"what-the-generator-does-not-solve",level:3}];function l(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:["\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"architecture-n-agents-in-the-same-file",children:"Architecture: N agents in the same file"})}),"\n",(0,i.jsx)(n.h2,{id:"why-generated-by-script",children:"Why this document is generated by a script"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," is not hand-written. It is generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-arch.mjs"}),",\nand the reason is in the script's header (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:5-8"}),"):"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The hand-written ",(0,i.jsx)(n.code,{children:"ARCH.md"}),' said "game.js (3234 lines)" when the file had 5361.\nEvery ',(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointer in the conflict table was off \u2014 and that\ntable is precisely what keeps two agents (or two contributors) from editing the\nsame region. A hand-written line-number index goes stale on the first\ncommit; the only fix is to generate it."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the separation that makes this work (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:11-13"}),", quoted verbatim \u2014\nthe source comments are in Portuguese):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"frente -> S\xcdMBOLO = conhecimento humano, est\xe1vel, vive nas FRENTES do script\ns\xedmbolo -> LINHA = vol\xe1til, \xe9 o que este script resolve toda vez\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:'Translation: "front \u2192 SYMBOL = human knowledge, stable, lives in the script\'s FRENTES;\nsymbol \u2192 LINE = volatile, it is what this script resolves every time."'})}),"\n",(0,i.jsxs)(n.p,{children:["The old ",(0,i.jsx)(n.code,{children:"ARCH.md"})," pinned ",(0,i.jsx)(n.strong,{children:"front \u2192 line"}),", mixing the two shelf lives. It is\na small idea with a big consequence: the work partition is declared in terms\nthat do not change (method names), and the resolution to volatile coordinates (line\nnumbers) is recomputed on every run."]}),"\n",(0,i.jsxs)(n.admonition,{type:"note",children:[(0,i.jsxs)(n.mdxAdmonitionTitle,{children:["The ",(0,i.jsx)(n.code,{children:"arch:check"})," is RED right now \u2014 and that is the best demonstration on this page"]}),(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"npm run arch"})," and ",(0,i.jsx)(n.code,{children:"npm run arch:check"})," exist today in the root ",(0,i.jsx)(n.code,{children:"package.json"}),", and the check\nis not passing. Gate output below, quoted verbatim \u2014 the tools print in Portuguese:"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"$ npm run arch:check\n\u2717 ARCH1 ARCH.md est\xe1 DESATUALIZADO em rela\xe7\xe3o ao c\xf3digo.\n game.js tem 6428 linhas; o \xedndice do ARCH.md n\xe3o bate.\n Rode: npm run arch\n"})}),(0,i.jsxs)(n.p,{children:["The message misleads on purpose: it ",(0,i.jsx)(n.strong,{children:"talks about lines because that is the summary it\nknows how to print"}),", but what ",(0,i.jsx)(n.code,{children:"--check"})," compares is the entire generated block, byte by byte \u2014 and that\nblock also carries the game's version number. Correct symbol index plus stale version gives\nthe same red. One command fixes it."]}),(0,i.jsxs)(n.p,{children:["A caveat that still holds: in CI the step has ",(0,i.jsx)(n.code,{children:"continue-on-error: true"}),", so the\ncheck runs but ",(0,i.jsx)(n.strong,{children:"does not block"})," \u2014 which is exactly how it managed to stay\nred without anyone noticing. Removing that line is what turns it into a real\ngate."]})]}),"\n",(0,i.jsx)(n.h2,{id:"indexed-files",children:"The indexed files"}),"\n",(0,i.jsxs)(n.p,{children:["Size of the files ",(0,i.jsx)(n.code,{children:"gen-arch.mjs"})," indexes \u2014 a generated block, regenerated by\n",(0,i.jsx)(n.code,{children:"npm run docs"})," and checked by ",(0,i.jsx)(n.code,{children:"npm run docs:check"}),":"]}),"\n","\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"File"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Lines"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/game.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"6,910"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/main.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"2,698"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/characters.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"1,068"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/glbchars.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"844"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/vmattach.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"628"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/weapons.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"346"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"public/js/springs.js"})}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"260"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:["Total in ",(0,i.jsx)(n.code,{children:"public/js/"}),": ",(0,i.jsx)(n.strong,{children:"32,001 lines in 44 files"}),". The symbol-to-line index lives in ",(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"wc -l public/js/*.js"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.h3,{id:"largest-methods",children:["The largest methods in ",(0,i.jsx)(n.code,{children:"game.js"})," \u2014 where the conflict lives"]}),"\n",(0,i.jsxs)(n.p,{children:["This table is ",(0,i.jsx)(n.strong,{children:"not reproduced here"}),", and the reason is this page's own thesis: it is\n",(0,i.jsx)(n.code,{children:"linha \u2192 m\xe9todo"}),", the volatile side of the separation, and duplicating it in a prose page creates a\nsecond copy that ages on its own. It lives generated, in one place only:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"npm run arch # regenerates tools/eval/ARCH.md\nnode tools/gen-arch.mjs --json # the raw index, for another tool\n"})}),"\n",(0,i.jsxs)(n.p,{children:["What does ",(0,i.jsx)(n.strong,{children:"not"})," age, and is therefore written here: ",(0,i.jsx)(n.code,{children:"_updateBot()"})," is by far the largest\nmethod in the file and is flagged by the index itself as an ",(0,i.jsx)(n.strong,{children:"extraction candidate"}),";\n",(0,i.jsx)(n.code,{children:"constructor()"}),", ",(0,i.jsx)(n.code,{children:"update()"})," and ",(0,i.jsx)(n.code,{children:"_dom()"})," are ",(0,i.jsx)(n.strong,{children:"red zone, append-only"}),", because any\nfront may need them. Big method = unreviewable PR and conflicting merge \u2014 extracting\n",(0,i.jsx)(n.code,{children:"_updateBot"})," is high-value, medium-risk work, and it requires coordinating first, because the\nregion is contested."]}),"\n",(0,i.jsx)(n.h2,{id:"disjoint-line-ranges",children:"Disjoint line ranges"}),"\n",(0,i.jsxs)(n.p,{children:["This is the mechanism that lets several agents (or contributors) edit the ",(0,i.jsx)(n.strong,{children:"same\nfile"})," \u2014 the largest in the repository, thousands of lines \u2014 at the same time, without merge\nconflict."]}),"\n",(0,i.jsx)(n.h3,{id:"how-it-works",children:"How it works"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Each front declares SYMBOLS, never lines."})," In ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:32-73"}),", the\n",(0,i.jsx)(n.code,{children:"FRENTES"})," constant lists, per front, three things: exclusive ",(0,i.jsx)(n.code,{children:"arquivos"}),", ",(0,i.jsx)(n.code,{children:"simbolos"}),"\n(methods) and ",(0,i.jsx)(n.code,{children:"consts"})," (top-level constants). For example, the ARMAS/VIEWMODEL front owns\n",(0,i.jsx)(n.code,{children:"_buildViewModels"}),", ",(0,i.jsx)(n.code,{children:"_vmFrame"}),", ",(0,i.jsx)(n.code,{children:"_tryShoot"}),", ",(0,i.jsx)(n.code,{children:"_shotRecoil"}),"\u2026 and the constants ",(0,i.jsx)(n.code,{children:"WEAPONS"}),",\n",(0,i.jsx)(n.code,{children:"VM_FOV_DEFAULT"}),", ",(0,i.jsx)(n.code,{children:"VM_OFF"}),", ",(0,i.jsx)(n.code,{children:"REC_DEG"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The script indexes the file and resolves symbol \u2192 range."})," ",(0,i.jsx)(n.code,{children:"indexar()"}),"\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:80-111"}),") scans the file line by line with three patterns: class\nmethod (exactly 2 spaces of indentation), ",(0,i.jsx)(n.strong,{children:"arrow method assigned at runtime"}),"\n(",(0,i.jsx)(n.code,{children:"this._vmFrame = (force) => {"}),") and top-level declaration. Each symbol ends where the\nnext one begins."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Contiguous ranges are merged"})," (gap \u2264 12 lines) to keep the table readable\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:172-178"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Overlap between fronts is detected"}),", because a conflict table that contradicts\nitself is worse than none (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:190-200"}),")."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["The detail in step 2 deserves highlighting: v1 of the script only saw class methods, so\n",(0,i.jsx)(n.code,{children:"_vmFrame"})," \u2014 about 100 lines born ",(0,i.jsx)(n.strong,{children:"inside"})," another method, as an arrow that\ncloses over local variables \u2014 was ",(0,i.jsx)(n.strong,{children:"invisible in the index"}),"\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:95-97"}),"). An index that cannot see the most contested method in the file is\nworse than no index at all, because it gives false confidence."]}),"\n",(0,i.jsx)(n.h3,{id:"conflict-table",children:"The conflict table"}),"\n",(0,i.jsxs)(n.p,{children:["From ",(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," (generated block \u2014 the ranges below are from the previous generation; run\n",(0,i.jsx)(n.code,{children:"node tools/gen-arch.mjs"})," for today's):"]}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Front"}),(0,i.jsx)(n.th,{children:"Exclusive files"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"ARMAS / VIEWMODEL"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"vmattach.js"})," ",(0,i.jsx)(n.code,{children:"springs.js"})," ",(0,i.jsx)(n.code,{children:"weapons.js"})," ",(0,i.jsx)(n.code,{children:"fparms.js"})," ",(0,i.jsx)(n.code,{children:"handik.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"BOTS / JOGABILIDADE"})}),(0,i.jsxs)(n.td,{children:["\u2014 (ranges in ",(0,i.jsx)(n.code,{children:"game.js"})," only)"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"MAPAS / MUNDO"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"maps.js"})," ",(0,i.jsx)(n.code,{children:"mapprops.js"})," ",(0,i.jsx)(n.code,{children:"map_brasilia.js"})," ",(0,i.jsx)(n.code,{children:"map_havan.js"})," ",(0,i.jsx)(n.code,{children:"map_piscina.js"})," ",(0,i.jsx)(n.code,{children:"map_piscinao_ramos.js"})," ",(0,i.jsx)(n.code,{children:"map_ferrovelho.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"GR\xc1FICOS / FX"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"bloom.js"})," ",(0,i.jsx)(n.code,{children:"textures.js"})," ",(0,i.jsx)(n.code,{children:"vao.js"})," ",(0,i.jsx)(n.code,{children:"stylize.js"})," ",(0,i.jsx)(n.code,{children:"gpuparticles.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"UI / HUD / MENU"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"main.js"})," ",(0,i.jsx)(n.code,{children:"public/style.css"})," ",(0,i.jsx)(n.code,{children:"src/pages/index.astro"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"\xc1UDIO"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"audio.js"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"PERSONAGENS"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"characters.js"})," ",(0,i.jsx)(n.code,{children:"glbchars.js"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"SITE / BACKEND"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"src/"})})]})]})]}),"\n",(0,i.jsxs)(n.admonition,{title:"Two map files have NO declared owner",type:"caution",children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"map_quebrada.js"})," (1.319 lines, the newest map) and ",(0,i.jsx)(n.code,{children:"map_decals.js"})," ",(0,i.jsx)(n.strong,{children:"appear in no\nfront at all"})," in ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})," \u2014 the list above is a faithful copy of ",(0,i.jsx)(n.code,{children:"FRENTES"}),", and they\nare not there. Whoever edits those two collides with nobody ",(0,i.jsx)(n.em,{children:"according to the table"}),", which is\nprecisely the guarantee the table is supposed to give and does not. Adding them is one line in\n",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})," followed by ",(0,i.jsx)(n.code,{children:"npm run arch"}),"."]}),(0,i.jsxs)(n.p,{children:["(",(0,i.jsx)(n.code,{children:"map.js"})," was once listed here and ",(0,i.jsx)(n.strong,{children:"no longer exists"}),': it was the "Pra\xe7a (cl\xe1ssico)", deleted\nalong with the ',(0,i.jsx)(n.code,{children:"praca_old"})," map.)"]})]}),"\n",(0,i.jsx)(n.h3,{id:"red-zones",children:"The red zones"}),"\n",(0,i.jsxs)(n.p,{children:["Three methods are ",(0,i.jsx)(n.strong,{children:"append-only"}),", because any front may need them\n(",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:75-77"}),"):"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"update()"})," \u2014 the loop"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_dom()"})," \u2014 the HUD wiring"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"constructor()"})," \u2014 one of the largest methods in the file (today's size is in ",(0,i.jsx)(n.code,{children:"ARCH.md"}),")"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Editing the middle of these is the fastest way for two contributors to trample each other.\nAppend at the end; do not reorganize."}),"\n",(0,i.jsx)(n.h3,{id:"operating-rules",children:"The operating rules"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Declare your front before editing."})," If it is a human PR, say so in the description."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsxs)(n.strong,{children:["In ",(0,i.jsx)(n.code,{children:"game.js"}),", edit by chunk \u2014 never overwrite the whole file."]})," A\ntool that rewrites the file erases the work of whoever is on the other range."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Two fronts with disjoint ranges run in parallel."})," The generated ",(0,i.jsx)(n.code,{children:"ARCH.md"})," records\nthat this was measured: ",(0,i.jsx)(n.em,{children:'"3 agents edited disjoint ranges simultaneously with zero\ncontent conflict"'})," (",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs:163"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Touched a symbol? Move the name in the front's declaration, not the number."})," The script\nwarns when a declared symbol disappears from the code."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"Why this matters to you, human",type:"tip",children:(0,i.jsxs)(n.p,{children:["The same partition that prevents collisions between agents is what makes a PR of yours reviewable. A\nPR that touches ",(0,i.jsx)(n.code,{children:"_updateBot"})," + ",(0,i.jsx)(n.code,{children:"style.css"})," + ",(0,i.jsx)(n.code,{children:"map_havan.js"})," is three PRs hidden in one, and\nwill collide with three different fronts. One PR per front lands fast."]})}),"\n",(0,i.jsx)(n.h2,{id:"three-zones",children:"The three zones of the repository"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"public/ game vanilla ES modules, zero build, vendored Three.js\nsrc/ site Astro + Vercel adapter, SSR API routes\ntools/ harness .mjs/.py scripts \u2014 the ruler, the gate and the probes\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Versions, counts and what each tool does are in\n",(0,i.jsx)(n.a,{href:"/docs/en/stack",children:"Stack and tools"})," \u2014 ",(0,i.jsx)(n.strong,{children:"generated"}),", not hand-written."]}),"\n",(0,i.jsx)(n.p,{children:"The coupling between them is deliberately thin and worth understanding:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The site loads the game via import map"}),", in ",(0,i.jsx)(n.code,{children:"src/pages/index.astro:97-123"}),". It is the only\nplace where Astro knows the game's modules exist."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"The harness loads the game straight from disk"}),", with no browser: ",(0,i.jsx)(n.code,{children:"tools/eval/harness.mjs"})," stubs\nDOM/canvas/",(0,i.jsx)(n.code,{children:"fetch"})," and imports ",(0,i.jsx)(n.code,{children:"public/js/game.js"})," as a module. That is why the gate measures\nproduction code, not a reimplementation."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"tools/eval/serve.mjs:15"})})," bridges to the test case: it serves ",(0,i.jsx)(n.code,{children:"public/"})," and maps\n",(0,i.jsx)(n.code,{children:"/"})," to the ",(0,i.jsx)(n.code,{children:"index.astro"})," source, with no Astro in the path."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"practical-consequence",children:"Practical consequence"}),"\n",(0,i.jsxs)(n.p,{children:["The game ",(0,i.jsx)(n.strong,{children:"cannot"})," gain a runtime dependency or a build step. This is not\nconservatism: it is what lets ",(0,i.jsx)(n.code,{children:"harness.mjs"})," boot the ",(0,i.jsx)(n.code,{children:"Game"})," class in pure node\nin seconds, which is what makes the gate exist. A bundler in the middle would break the ruler (quality gate) along\nwith the portability."]}),"\n",(0,i.jsx)(n.h2,{id:"content-data-system",children:"Content data system"}),"\n",(0,i.jsxs)(n.p,{children:["Today maps, weapons and characters are ",(0,i.jsx)(n.strong,{children:"code"}),": each ",(0,i.jsx)(n.code,{children:"map_*.js"}),' is geometry declared by\nhand, and the largest of them rival the system modules in size. The\n"content as data" direction in\n',(0,i.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,i.jsx)(n.code,{children:"docs/ROADMAP.md"})}),"\nwants to migrate this to JSON with a single loader, so that a content contribution becomes\n",(0,i.jsx)(n.em,{children:'"open a JSON and create content"'})," instead of ",(0,i.jsx)(n.em,{children:'"a risky hand-coded code PR"'}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["If you want the highest-leverage work in the entire project, this is it. See\n",(0,i.jsx)(n.a,{href:"./status",children:"Current state"}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"generated-vs-not",children:"What is generated, and what is not"}),"\n",(0,i.jsx)(n.p,{children:"Two things in this repository are generated by script, and for the same reason:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Generated"}),(0,i.jsx)(n.th,{children:"Script"}),(0,i.jsx)(n.th,{children:"Gate"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"tools/eval/ARCH.md"})," \u2014 symbol\u2192line index and conflict table"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"npm run arch:check"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["The numeric blocks of ",(0,i.jsx)(n.code,{children:"README.md"})," and of this documentation"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"tools/gen-docs.mjs"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"npm run docs:check"})," (in ",(0,i.jsx)(n.code,{children:"check:fast"}),")"]})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"The rule that separates what goes in and what stays out:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Derivable from the code?"})," It becomes a generated block, between markers, with ",(0,i.jsx)(n.code,{children:"--check"})," in the gate.\nCounts of lines, of characters, of weapons, of maps, of scripts, of invariants,\nversion, the ",(0,i.jsx)(n.code,{children:"package.json"})," script list, dependency version."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Not derivable?"})," Then it is a decision or an explanation \u2014 and it ",(0,i.jsx)(n.strong,{children:"must not contain a number that\nages"}),". Write it without the number, or cite the command that produces it. The gate's scoreboard,\nfor example, depends on which inputs exist on the machine: it lives pasted from a real\nrun in ",(0,i.jsx)(n.code,{children:"KNOWN-BUGS.md"}),", not repeated across five pages."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the reason ",(0,i.jsx)(n.code,{children:"--check"})," is in the gate, not merely available: ",(0,i.jsx)(n.strong,{children:"what does not become a ruler is\noptimized away."})," A generator nobody is forced to run goes stale in a week,\nand then the documentation is back to lying with the appearance of rigor \u2014 which is worse than lying without\nit."]}),"\n",(0,i.jsxs)(n.admonition,{title:"Where you put the new gate in the chain matters",type:"danger",children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"check:fast"})," is a chain of ",(0,i.jsx)(n.code,{children:"&&"}),": the first error cuts off the rest. ",(0,i.jsx)(n.code,{children:"arch:check"})," has been\nred for days, so ",(0,i.jsx)(n.strong,{children:"every gate placed after it is born dead"})," \u2014 it runs zero times\nand nobody notices, because the output stops earlier. That is exactly what happened to the first\nversion of ",(0,i.jsx)(n.code,{children:"docs:check"}),", and it is the same failure mode as BUG-02 (the gate measuring the viewmodel\nfrom yesterday because the ",(0,i.jsx)(n.code,{children:"&&"})," cut off before the JSON was regenerated)."]}),(0,i.jsxs)(n.p,{children:["That is why ",(0,i.jsx)(n.code,{children:"docs:check"})," comes ",(0,i.jsx)(n.strong,{children:"before"})," ",(0,i.jsx)(n.code,{children:"arch:check"})," in ",(0,i.jsx)(n.code,{children:"package.json"}),", with the reason\nwritten in the ",(0,i.jsx)(n.code,{children:"//check:fast"})," key. When ",(0,i.jsx)(n.code,{children:"ARCH.md"})," is regenerated and ",(0,i.jsx)(n.code,{children:"arch:check"})," goes\ngreen again, the order stops mattering; until then, it matters."]})]}),"\n",(0,i.jsxs)(n.p,{children:["Pasting a new block is writing the marker and running ",(0,i.jsx)(n.code,{children:"npm run docs"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"{/* BEGIN:GERADO:BLOCK_NAME \u2014 n\xe3o edite \xe0 m\xe3o, rode `npm run docs` */}\n{/* END:GERADO:BLOCK_NAME */}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["(",(0,i.jsx)(n.code,{children:"BLOCK_NAME"})," is one of the keys of the ",(0,i.jsx)(n.code,{children:"BLOCOS"})," object at the top of ",(0,i.jsx)(n.code,{children:"gen-docs.mjs"}),". A\ndeclared block that nobody consumes becomes a loud warning in the output \u2014 an orphan block is dead code that\npretends to be documentation.)"]}),"\n",(0,i.jsxs)(n.p,{children:["In plain Markdown (",(0,i.jsx)(n.code,{children:"README.md"}),") the marker is an HTML comment (",(0,i.jsx)(n.code,{children:"\x3c!-- BEGIN:GERADO:\u2026 --\x3e"}),").\nIn the pages of this doc it is an ",(0,i.jsx)(n.strong,{children:"MDX"})," comment (",(0,i.jsx)(n.code,{children:"{/* \u2026 */}"}),"): Docusaurus 3 compiles ",(0,i.jsx)(n.code,{children:".md"}),"\nas MDX, and an HTML comment there is a parse error that takes down the build. The generator accepts both\nsyntaxes and preserves whichever it finds."]}),"\n",(0,i.jsxs)(n.h3,{id:"what-the-generator-does-not-solve",children:["What the generator does NOT solve: ",(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointers in prose"]}),"\n",(0,i.jsxs)(n.p,{children:["A ",(0,i.jsx)(n.code,{children:"game.js:5361"})," written in the middle of a paragraph is the cheap version of the same defect \u2014 it\npoints to the wrong place at the first commit that touches the file. It cannot be generated (the\npointer is part of the sentence), but the ",(0,i.jsx)(n.strong,{children:"gross case can be detected"}),": a pointer that points\npast the end of the file."]}),"\n","\n",(0,i.jsxs)(n.p,{children:["No ",(0,i.jsx)(n.code,{children:"arquivo:linha"})," pointer in the docs points outside the file it cites. \u2713"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["This checks only the file's ",(0,i.jsx)(n.strong,{children:"bound"}),": a pointer that still fits but changed subject passes here. That is why the house doctrine is to declare the SYMBOL and leave the line to the generator \u2014 see ",(0,i.jsx)(n.code,{children:"tools/gen-arch.mjs"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"sweep of "}),"arquivo",":linha",(0,i.jsx)(n.code,{children:" across README/STATUS/HANDOFF/KNOWN-BUGS/docs/docs/SKILL"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.p,{children:["That is why the doctrine is to declare the ",(0,i.jsx)(n.strong,{children:"symbol"})," and leave the line to the generator. When the\n",(0,i.jsx)(n.code,{children:"arquivo:linha"})," really is necessary, cite alongside it the name of what lives there \u2014 that way whoever reads it\na month from now finds it via ",(0,i.jsx)(n.code,{children:"grep"})," even with the pointer shifted."]})]})}function a(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453(e,n,s){s.d(n,{R:()=>o,x:()=>d});var t=s(6540);const i={},r=t.createContext(i);function o(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/en/assets/js/a1985327.b0b84147.js b/public/docs/en/assets/js/a1985327.de92e49f.js
similarity index 99%
rename from public/docs/en/assets/js/a1985327.b0b84147.js
rename to public/docs/en/assets/js/a1985327.de92e49f.js
index 421f427bc..7d7dd617b 100644
--- a/public/docs/en/assets/js/a1985327.b0b84147.js
+++ b/public/docs/en/assets/js/a1985327.de92e49f.js
@@ -1 +1 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[515],{2102(e,n,s){s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>d,default:()=>l,frontMatter:()=>o,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"colaborar","title":"How to contribute","description":"Setup, how to run the gate, what a PR needs, how to add a weapon / character / map, and the good first tasks.","source":"@site/i18n/en/docusaurus-plugin-content-docs/current/colaborar.md","sourceDirName":".","slug":"/contributing","permalink":"/docs/en/contributing","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/colaborar.md","tags":[],"version":"current","sidebarPosition":6,"frontMatter":{"id":"colaborar","title":"How to contribute","sidebar_label":"How to contribute","sidebar_position":6,"slug":"/contributing","description":"Setup, how to run the gate, what a PR needs, how to add a weapon / character / map, and the good first tasks."},"sidebar":"dev","previous":{"title":"Architecture","permalink":"/docs/en/architecture"},"next":{"title":"Current state","permalink":"/docs/en/status"}}');var t=s(4848),r=s(8453);const o={id:"colaborar",title:"How to contribute",sidebar_label:"How to contribute",sidebar_position:6,slug:"/contributing",description:"Setup, how to run the gate, what a PR needs, how to add a weapon / character / map, and the good first tasks."},d="How to contribute",a={},c=[{value:"Setup",id:"setup",level:2},{value:"Running the gate",id:"running-the-gate",level:2},{value:"Before saying you fixed it: mutate",id:"mutate-before-claiming",level:3},{value:"If your PR is a bug fix",id:"bug-fix-pr",level:3},{value:"What a PR needs",id:"what-a-pr-needs",level:2},{value:"1. A new invariant \u2014 or the reason it doesn't need one",id:"1-new-invariant",level:3},{value:"2. The gate cannot get worse",id:"2-gate-cannot-get-worse",level:3},{value:"3. Numbers, with arquivo:linha",id:"3-numbers-with-file-line",level:3},{value:"4. One front per PR",id:"4-one-front-per-pr",level:3},{value:"5. Repository hygiene",id:"5-repo-hygiene",level:3},{value:"6. Editorial line",id:"6-editorial-line",level:3},{value:"How to add a weapon",id:"how-to-add-a-weapon",level:2},{value:"How to add a character",id:"how-to-add-a-character",level:2},{value:"How to add a map",id:"how-to-add-a-map",level:2},{value:"Good first tasks",id:"good-first-tasks",level:2},{value:"Very good for the first PR",id:"very-good-first-pr",level:3},{value:"Real work, still accessible",id:"real-work-still-accessible",level:3},{value:"High value, needs a conversation first",id:"high-value-needs-conversation",level:3},{value:"Process",id:"process",level:2}];function h(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:["\n",(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"how-to-contribute",children:"How to contribute"})}),"\n",(0,t.jsxs)(n.p,{children:["The number below is not rhetoric, and it is not hand-written: it comes from ",(0,t.jsx)(n.code,{children:"git shortlog -sn --no-merges"})," minus the authors that are AI agents (which sign as ",(0,t.jsx)(n.code,{children:"Claude"})," /\n",(0,t.jsx)(n.code,{children:"Claude (gauntlet \u2026)"}),")."]}),"\n","\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"11 human author identities"})," sign commits in this branch: ",(0,t.jsx)(n.code,{children:"ruben-cytonic"}),", ",(0,t.jsx)(n.code,{children:"Emerson Garrido"}),", ",(0,t.jsx)(n.code,{children:"Ruben"}),", ",(0,t.jsx)(n.code,{children:"rubenmarcus"}),", ",(0,t.jsx)(n.code,{children:"Ruben Marcus"}),", ",(0,t.jsx)(n.code,{children:"William Oliveira"}),", ",(0,t.jsx)(n.code,{children:"Juan Versolato Lopes"}),", ",(0,t.jsx)(n.code,{children:"daeeseD"}),", ",(0,t.jsx)(n.code,{children:"matheusgb"}),", ",(0,t.jsx)(n.code,{children:"Man\xe1 Soares"}),", ",(0,t.jsx)(n.code,{children:"daltonfontes"}),". Automated identities are excluded. A Git author name is not necessarily one unique person."]}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:["Block generated by ",(0,t.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,t.jsx)(n.code,{children:"git shortlog -sn --no-merges (descontando autores que s\xe3o agentes)"})]}),"\n"]}),"\n","\n",(0,t.jsx)(n.p,{children:"There is no team, there is no community, there is no reviewer queue \u2014 there are these\npeople and an automated gate."}),"\n",(0,t.jsx)(n.admonition,{title:"The block above counts the BRANCH, and the project is bigger than it",type:"note",children:(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"main"})," has a fourth contributor that this working branch does not contain \u2014 13 commits\nof a desktop client, merged in July. Who, how much, and why this matters for any\nlicensing decision is in ",(0,t.jsx)(n.code,{children:"CONTRIBUTING.md"})," (license section and surfaces)."]})}),"\n",(0,t.jsxs)(n.p,{children:["This is relevant to you in two opposite ways. The bad one: if your PR gets stuck, it can\ntake a while. The good one: ",(0,t.jsx)(n.strong,{children:"almost the entire ruler (quality gate) is machine."})," ",(0,t.jsx)(n.code,{children:"npm run check"})," gives\nyou the same verdict the maintainer would, before you open the PR, without waiting for\nanyone. The barrier is low ",(0,t.jsx)(n.strong,{children:"on purpose"})," \u2014 it is one of the principles that do not change in\n",(0,t.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,t.jsx)(n.code,{children:"docs/ROADMAP.md"})}),".\nBut the ruler is not."]}),"\n",(0,t.jsxs)(n.p,{children:["One-sentence summary: ",(0,t.jsx)(n.strong,{children:"bring the number."})," A PR that changes visible behavior and brings\nneither a new invariant nor the reason it does not need one will come back with a question."]}),"\n",(0,t.jsx)(n.h2,{id:"setup",children:"Setup"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # http://localhost:4321 \u2014 the root route IS the game\n"})}),"\n",(0,t.jsx)(n.p,{children:"Optional:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"npm run fetch-audio # audio pack (without it: synthesized sounds)\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Requirements: Node 22 (it is what the CI uses, ",(0,t.jsx)(n.code,{children:".github/workflows/ci.yml:14"}),") and Python 3 for\npart of the harness (",(0,t.jsx)(n.code,{children:"ref-measure.py"}),", ",(0,t.jsx)(n.code,{children:"char_probe.py"}),", ",(0,t.jsx)(n.code,{children:"mat_shade.py"})," \u2014 they use numpy and PIL)."]}),"\n",(0,t.jsxs)(n.admonition,{type:"caution",children:[(0,t.jsxs)(n.mdxAdmonitionTitle,{children:["Serving ",(0,t.jsx)(n.code,{children:"public/"})," does NOT run the game"]}),(0,t.jsxs)(n.p,{children:["There is no ",(0,t.jsx)(n.code,{children:"public/index.html"}),": the game's HTML is ",(0,t.jsx)(n.code,{children:"src/pages/index.astro"}),", at the root route.\nUse ",(0,t.jsx)(n.code,{children:"npm run dev"}),". Details and proof in\n",(0,t.jsx)(n.a,{href:"/docs/en/#the-first-hour-gotcha",children:"Getting started"}),"."]})]}),"\n",(0,t.jsx)(n.h2,{id:"running-the-gate",children:"Running the gate"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"npm run eval:vm # MANDATORY FIRST \u2014 see the warning below\nnode tools/eval/invariants.mjs # the whole gate\nnode tools/eval/invariants.mjs --json # machine-readable output\nnpm run check # syntax + vm + gate + recoil + bots\n"})}),"\n",(0,t.jsxs)(n.admonition,{type:"danger",children:[(0,t.jsxs)(n.mdxAdmonitionTitle,{children:[(0,t.jsx)(n.code,{children:"eval:vm"})," runs BEFORE ",(0,t.jsx)(n.code,{children:"invariants.mjs"}),". Always."]}),(0,t.jsxs)(n.p,{children:["The viewmodel invariants (VM1\u2013VM19) ",(0,t.jsx)(n.strong,{children:"read"})," ",(0,t.jsx)(n.code,{children:"tools/eval/vm_mint_audit.json"}),", which is what\n",(0,t.jsx)(n.code,{children:"eval:vm"})," ",(0,t.jsx)(n.strong,{children:"writes"}),". Running the invariants with that JSON stale measures yesterday's\nviewmodel and ",(0,t.jsx)(n.strong,{children:"invents reds"}),": on 04/08/2026 the JSON was at ",(0,t.jsx)(n.code,{children:"V0=80\xb0"})," against ",(0,t.jsx)(n.code,{children:"game.js"}),"\nat ",(0,t.jsx)(n.code,{children:"V0=42\xb0"}),", and VM5 flagged ",(0,t.jsx)(n.strong,{children:"26/26 weapons out"}),"; after ",(0,t.jsx)(n.code,{children:"npm run eval:vm"}),", ",(0,t.jsx)(n.strong,{children:"3/26"}),".\nVM1 dropped from 26/26 to 2/26 and VM9 went green."]}),(0,t.jsxs)(n.p,{children:["The order of ",(0,t.jsx)(n.code,{children:"npm run check"})," has already been fixed (",(0,t.jsx)(n.code,{children:"package.json"}),") \u2014 the care is for when\nyou call ",(0,t.jsx)(n.code,{children:"node tools/eval/invariants.mjs"})," by hand. Details: BUG-02 in\n",(0,t.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"})}),"."]})]}),"\n",(0,t.jsxs)(n.p,{children:["Real cost: on a 2-CPU machine, ",(0,t.jsx)(n.strong,{children:"about 10 minutes"}),". It boots the real game five\ntimes (once per map), runs 60 s of bot simulation per map and audits every weapon GLB.\nRun it before opening the PR, not after receiving the review."]}),"\n",(0,t.jsx)(n.p,{children:"Individual harnesses, for when you want to iterate fast on a single front:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"node tools/eval/vm-mint-audit.mjs # viewmodel framing (the whole arsenal)\nnode tools/eval/vm-solve.mjs # does a feasible point exist for the VM invariants?\nnode tools/eval/vm-solve.mjs --atual # only the margins of the current config (instant)\nnode tools/eval/botsim.mjs 60 all # bot navigation, all maps, fixed seeds\nnode tools/eval/char-probe.mjs # characters (C1..C6)\nnode tools/eval/map-check.mjs all # map geometry (MAP1-MAP3, CTF1)\nnode tools/eval/mat-check.mjs # material/light/fog/texture\nnode tools/eval/pickup-check.mjs # is every pickup reachable?\nnode tools/eval/ui-check.mjs # UI1 contrast \xb7 UI2 clutter \xb7 UI3 dead area \xb7 UI4 rhythm\n"})}),"\n",(0,t.jsx)(n.h3,{id:"mutate-before-claiming",children:"Before saying you fixed it: mutate"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # expects UI1 to go RED\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Undo your own fix and check that the gate ",(0,t.jsx)(n.strong,{children:"goes red"}),". If it stays green,\nwhat you measured is not what you fixed. It is the most expensive lesson in this repository and it\nhas a whole page: ",(0,t.jsx)(n.a,{href:"/docs/en/quality-gates#mutation-test",children:"Mutation test"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"bug-fix-pr",children:"If your PR is a bug fix"}),"\n",(0,t.jsxs)(n.p,{children:["Use the ",(0,t.jsx)(n.code,{children:"bug-hunt"})," skill (",(0,t.jsx)(n.code,{children:".claude/skills/bug-hunt/SKILL.md"}),"). It is the step-by-step of this\ndoctrine applied to defects \u2014 with the real case that bought each rule, the template for the\n",(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"})," entry and the one for the final report, including how to declare what you did ",(0,t.jsx)(n.strong,{children:"not"}),"\nverify. It works for agents and for people."]}),"\n",(0,t.jsx)(n.h2,{id:"what-a-pr-needs",children:"What a PR needs"}),"\n",(0,t.jsx)(n.h3,{id:"1-new-invariant",children:"1. A new invariant \u2014 or the reason it doesn't need one"}),"\n",(0,t.jsxs)(n.p,{children:["This is the rule that defines the project. Every PR that changes ",(0,t.jsx)(n.strong,{children:"observable behavior"})," brings\none of two things:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"A new invariant"})," in ",(0,t.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),", with a ceiling that has provenance\n(reference file + measured pixel + script that reproduces it), or"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"One sentence in the PR description"}),' saying why it does not need one. Valid reasons: "it is\nalready covered by invariant X" (say which), "it is a refactor with no observable change \u2014 the\ngate gives the same score before and after" (paste both), "it is pure content (text,\nasset) with no game rule attached".']}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:'Invalid reason: "I tested it manually and it looked good".'}),"\n",(0,t.jsxs)(n.p,{children:["Why: ",(0,t.jsx)(n.strong,{children:"intent that does not become an invariant gets optimized away"}),". One round took the\ngate from 16/21 to 19/21 without loosening a single ceiling, and was rejected, because it\nsilently destroyed an aesthetic decision that no invariant encoded. Full case in\n",(0,t.jsx)(n.a,{href:"/docs/en/quality-gates#law-1",children:"The gate"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"2-gate-cannot-get-worse",children:"2. The gate cannot get worse"}),"\n",(0,t.jsxs)(n.p,{children:["Paste the output of ",(0,t.jsx)(n.code,{children:"node tools/eval/invariants.mjs"})," before and after. If any critical one went\nred, the PR does not go in. If you ",(0,t.jsx)(n.strong,{children:"fixed"})," a red one, say which and show it."]}),"\n",(0,t.jsxs)(n.p,{children:["The gate is red today (",(0,t.jsx)(n.a,{href:"./status",children:"see which ones"}),", and the living list with root causes is\nin ",(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"}),"). That is not a license to make it worse: the commitment is ",(0,t.jsx)(n.em,{children:'"your change\nadds no red"'}),"."]}),"\n",(0,t.jsxs)(n.h3,{id:"3-numbers-with-file-line",children:["3. Numbers, with ",(0,t.jsx)(n.code,{children:"arquivo:linha"})]}),"\n",(0,t.jsxs)(n.p,{children:['The claim "I improved the lighting" is not reviewable. "The ',(0,t.jsx)(n.code,{children:"praca_poderes"})," floor was 8 points\nof L* above the walls, cause at ",(0,t.jsx)(n.code,{children:"map_brasilia.js:NNN"}),', fixed to X" is. This\nrequirement is not style \u2014 it is what allows the next round to check your work.']}),"\n",(0,t.jsx)(n.h3,{id:"4-one-front-per-pr",children:"4. One front per PR"}),"\n",(0,t.jsxs)(n.p,{children:["Consult the conflict table (",(0,t.jsx)(n.a,{href:"/docs/en/architecture#conflict-table",children:"Architecture"}),"). A\nPR that touches weapons + UI + map is three hidden PRs, and will collide with three fronts. In\n",(0,t.jsx)(n.code,{children:"game.js"}),", edit by section; never overwrite the whole file."]}),"\n",(0,t.jsx)(n.h3,{id:"5-repo-hygiene",children:"5. Repository hygiene"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"node --check"})," on every file in ",(0,t.jsx)(n.code,{children:"public/js/"})," that you edited (the CI does this first,\n",(0,t.jsx)(n.code,{children:".github/workflows/ci.yml:19-20"}),")."]}),"\n",(0,t.jsxs)(n.li,{children:["Comments ",(0,t.jsx)(n.strong,{children:"in Portuguese explaining the why"}),", not the what. It is the repo's culture and it is\nwhat survives the next handoff."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Never delete a provenance comment"})," in a cleanup PR. That long paragraph\nexplaining where the number 0,513 came from is what keeps the next round from repeating three\nlost days."]}),"\n",(0,t.jsxs)(n.li,{children:["Touched ",(0,t.jsx)(n.code,{children:"public/js/*.js"}),"? ",(0,t.jsxs)(n.strong,{children:["Bump the ",(0,t.jsx)(n.code,{children:"?v="})," on both sides"]})," \u2014 ",(0,t.jsx)(n.code,{children:"public/js/version.js"})," and the\nimport map of ",(0,t.jsx)(n.code,{children:"src/pages/index.astro"}),'. It has already cost days of "fixes that never arrived".']}),"\n",(0,t.jsxs)(n.li,{children:["Touched ",(0,t.jsx)(n.code,{children:"public/js/*.js"}),", ",(0,t.jsx)(n.code,{children:"maps.js"}),", ",(0,t.jsx)(n.code,{children:"characters.js"}),", or a dependency?\n",(0,t.jsxs)(n.strong,{children:["Run ",(0,t.jsx)(n.code,{children:"npm run docs"})]})," and commit it along. ",(0,t.jsx)(n.code,{children:"docs:check"})," is in ",(0,t.jsx)(n.code,{children:"check:fast"})," and will\nfail \u2014 it takes less than a second and is what keeps the doc from lying again."]}),"\n",(0,t.jsxs)(n.li,{children:["No copyrighted assets. No committed ",(0,t.jsx)(n.code,{children:"service_role"})," key."]}),"\n",(0,t.jsx)(n.li,{children:"No runtime dependency in the game. Three.js is vendored; the game has to run by\ndragging the folder onto a static host."}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"6-editorial-line",children:"6. Editorial line"}),"\n",(0,t.jsxs)(n.p,{children:["From ",(0,t.jsx)(n.code,{children:"CONTRIBUTING.md:7-16"}),": the game has no political side (both teams have the same\nmechanics), does not incite hatred, does not use real people \u2014 only original archetypes, no gore.\nContributions that violate this are rejected. It is not bureaucracy: it is what protects the project\nfrom takedowns and from becoming something else."]}),"\n",(0,t.jsx)(n.h2,{id:"how-to-add-a-weapon",children:"How to add a weapon"}),"\n",(0,t.jsxs)(n.p,{children:["The pipeline is data-driven from the GLB. Weapon GLBs live in ",(0,t.jsx)(n.code,{children:"public/models/weapons/"}),"\n(the count is in the generated block of ",(0,t.jsx)(n.a,{href:"/docs/en/",children:"Getting started"}),")."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Put the GLB"})," at ",(0,t.jsx)(n.code,{children:"public/models/weapons/arquivo:linha",id:"3-numbers-with-file-line",level:3},{value:"4. One front per PR",id:"4-one-front-per-pr",level:3},{value:"5. Repository hygiene",id:"5-repo-hygiene",level:3},{value:"6. Editorial line",id:"6-editorial-line",level:3},{value:"How to add a weapon",id:"how-to-add-a-weapon",level:2},{value:"How to add a character",id:"how-to-add-a-character",level:2},{value:"How to add a map",id:"how-to-add-a-map",level:2},{value:"Good first tasks",id:"good-first-tasks",level:2},{value:"Very good for the first PR",id:"very-good-first-pr",level:3},{value:"Real work, still accessible",id:"real-work-still-accessible",level:3},{value:"High value, needs a conversation first",id:"high-value-needs-conversation",level:3},{value:"Process",id:"process",level:2}];function h(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:["\n",(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"how-to-contribute",children:"How to contribute"})}),"\n",(0,t.jsxs)(n.p,{children:["The number below is not rhetoric, and it is not hand-written: it comes from ",(0,t.jsx)(n.code,{children:"git shortlog -sn --no-merges"})," minus the authors that are AI agents (which sign as ",(0,t.jsx)(n.code,{children:"Claude"})," /\n",(0,t.jsx)(n.code,{children:"Claude (gauntlet \u2026)"}),")."]}),"\n","\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"11 human author identities"})," sign commits in this branch: ",(0,t.jsx)(n.code,{children:"ruben-cytonic"}),", ",(0,t.jsx)(n.code,{children:"Ruben"}),", ",(0,t.jsx)(n.code,{children:"Emerson Garrido"}),", ",(0,t.jsx)(n.code,{children:"rubenmarcus"}),", ",(0,t.jsx)(n.code,{children:"Ruben Marcus"}),", ",(0,t.jsx)(n.code,{children:"William Oliveira"}),", ",(0,t.jsx)(n.code,{children:"Juan Versolato Lopes"}),", ",(0,t.jsx)(n.code,{children:"daeeseD"}),", ",(0,t.jsx)(n.code,{children:"matheusgb"}),", ",(0,t.jsx)(n.code,{children:"Man\xe1 Soares"}),", ",(0,t.jsx)(n.code,{children:"daltonfontes"}),". Automated identities are excluded. A Git author name is not necessarily one unique person."]}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:["Block generated by ",(0,t.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,t.jsx)(n.code,{children:"git shortlog -sn --no-merges (descontando autores que s\xe3o agentes)"})]}),"\n"]}),"\n","\n",(0,t.jsx)(n.p,{children:"There is no team, there is no community, there is no reviewer queue \u2014 there are these\npeople and an automated gate."}),"\n",(0,t.jsx)(n.admonition,{title:"The block above counts the BRANCH, and the project is bigger than it",type:"note",children:(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"main"})," has a fourth contributor that this working branch does not contain \u2014 13 commits\nof a desktop client, merged in July. Who, how much, and why this matters for any\nlicensing decision is in ",(0,t.jsx)(n.code,{children:"CONTRIBUTING.md"})," (license section and surfaces)."]})}),"\n",(0,t.jsxs)(n.p,{children:["This is relevant to you in two opposite ways. The bad one: if your PR gets stuck, it can\ntake a while. The good one: ",(0,t.jsx)(n.strong,{children:"almost the entire ruler (quality gate) is machine."})," ",(0,t.jsx)(n.code,{children:"npm run check"})," gives\nyou the same verdict the maintainer would, before you open the PR, without waiting for\nanyone. The barrier is low ",(0,t.jsx)(n.strong,{children:"on purpose"})," \u2014 it is one of the principles that do not change in\n",(0,t.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/docs/ROADMAP.md",children:(0,t.jsx)(n.code,{children:"docs/ROADMAP.md"})}),".\nBut the ruler is not."]}),"\n",(0,t.jsxs)(n.p,{children:["One-sentence summary: ",(0,t.jsx)(n.strong,{children:"bring the number."})," A PR that changes visible behavior and brings\nneither a new invariant nor the reason it does not need one will come back with a question."]}),"\n",(0,t.jsx)(n.h2,{id:"setup",children:"Setup"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/rubenmarcus/csbrasil.git && cd csbrasil\nnpm install\nnpm run dev # http://localhost:4321 \u2014 the root route IS the game\n"})}),"\n",(0,t.jsx)(n.p,{children:"Optional:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"npm run fetch-audio # audio pack (without it: synthesized sounds)\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Requirements: Node 22 (it is what the CI uses, ",(0,t.jsx)(n.code,{children:".github/workflows/ci.yml:14"}),") and Python 3 for\npart of the harness (",(0,t.jsx)(n.code,{children:"ref-measure.py"}),", ",(0,t.jsx)(n.code,{children:"char_probe.py"}),", ",(0,t.jsx)(n.code,{children:"mat_shade.py"})," \u2014 they use numpy and PIL)."]}),"\n",(0,t.jsxs)(n.admonition,{type:"caution",children:[(0,t.jsxs)(n.mdxAdmonitionTitle,{children:["Serving ",(0,t.jsx)(n.code,{children:"public/"})," does NOT run the game"]}),(0,t.jsxs)(n.p,{children:["There is no ",(0,t.jsx)(n.code,{children:"public/index.html"}),": the game's HTML is ",(0,t.jsx)(n.code,{children:"src/pages/index.astro"}),", at the root route.\nUse ",(0,t.jsx)(n.code,{children:"npm run dev"}),". Details and proof in\n",(0,t.jsx)(n.a,{href:"/docs/en/#the-first-hour-gotcha",children:"Getting started"}),"."]})]}),"\n",(0,t.jsx)(n.h2,{id:"running-the-gate",children:"Running the gate"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"npm run eval:vm # MANDATORY FIRST \u2014 see the warning below\nnode tools/eval/invariants.mjs # the whole gate\nnode tools/eval/invariants.mjs --json # machine-readable output\nnpm run check # syntax + vm + gate + recoil + bots\n"})}),"\n",(0,t.jsxs)(n.admonition,{type:"danger",children:[(0,t.jsxs)(n.mdxAdmonitionTitle,{children:[(0,t.jsx)(n.code,{children:"eval:vm"})," runs BEFORE ",(0,t.jsx)(n.code,{children:"invariants.mjs"}),". Always."]}),(0,t.jsxs)(n.p,{children:["The viewmodel invariants (VM1\u2013VM19) ",(0,t.jsx)(n.strong,{children:"read"})," ",(0,t.jsx)(n.code,{children:"tools/eval/vm_mint_audit.json"}),", which is what\n",(0,t.jsx)(n.code,{children:"eval:vm"})," ",(0,t.jsx)(n.strong,{children:"writes"}),". Running the invariants with that JSON stale measures yesterday's\nviewmodel and ",(0,t.jsx)(n.strong,{children:"invents reds"}),": on 04/08/2026 the JSON was at ",(0,t.jsx)(n.code,{children:"V0=80\xb0"})," against ",(0,t.jsx)(n.code,{children:"game.js"}),"\nat ",(0,t.jsx)(n.code,{children:"V0=42\xb0"}),", and VM5 flagged ",(0,t.jsx)(n.strong,{children:"26/26 weapons out"}),"; after ",(0,t.jsx)(n.code,{children:"npm run eval:vm"}),", ",(0,t.jsx)(n.strong,{children:"3/26"}),".\nVM1 dropped from 26/26 to 2/26 and VM9 went green."]}),(0,t.jsxs)(n.p,{children:["The order of ",(0,t.jsx)(n.code,{children:"npm run check"})," has already been fixed (",(0,t.jsx)(n.code,{children:"package.json"}),") \u2014 the care is for when\nyou call ",(0,t.jsx)(n.code,{children:"node tools/eval/invariants.mjs"})," by hand. Details: BUG-02 in\n",(0,t.jsx)(n.a,{href:"https://github.com/rubenmarcus/csbrasil/blob/main/KNOWN-BUGS.md",children:(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"})}),"."]})]}),"\n",(0,t.jsxs)(n.p,{children:["Real cost: on a 2-CPU machine, ",(0,t.jsx)(n.strong,{children:"about 10 minutes"}),". It boots the real game five\ntimes (once per map), runs 60 s of bot simulation per map and audits every weapon GLB.\nRun it before opening the PR, not after receiving the review."]}),"\n",(0,t.jsx)(n.p,{children:"Individual harnesses, for when you want to iterate fast on a single front:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"node tools/eval/vm-mint-audit.mjs # viewmodel framing (the whole arsenal)\nnode tools/eval/vm-solve.mjs # does a feasible point exist for the VM invariants?\nnode tools/eval/vm-solve.mjs --atual # only the margins of the current config (instant)\nnode tools/eval/botsim.mjs 60 all # bot navigation, all maps, fixed seeds\nnode tools/eval/char-probe.mjs # characters (C1..C6)\nnode tools/eval/map-check.mjs all # map geometry (MAP1-MAP3, CTF1)\nnode tools/eval/mat-check.mjs # material/light/fog/texture\nnode tools/eval/pickup-check.mjs # is every pickup reachable?\nnode tools/eval/ui-check.mjs # UI1 contrast \xb7 UI2 clutter \xb7 UI3 dead area \xb7 UI4 rhythm\n"})}),"\n",(0,t.jsx)(n.h3,{id:"mutate-before-claiming",children:"Before saying you fixed it: mutate"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # expects UI1 to go RED\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Undo your own fix and check that the gate ",(0,t.jsx)(n.strong,{children:"goes red"}),". If it stays green,\nwhat you measured is not what you fixed. It is the most expensive lesson in this repository and it\nhas a whole page: ",(0,t.jsx)(n.a,{href:"/docs/en/quality-gates#mutation-test",children:"Mutation test"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"bug-fix-pr",children:"If your PR is a bug fix"}),"\n",(0,t.jsxs)(n.p,{children:["Use the ",(0,t.jsx)(n.code,{children:"bug-hunt"})," skill (",(0,t.jsx)(n.code,{children:".claude/skills/bug-hunt/SKILL.md"}),"). It is the step-by-step of this\ndoctrine applied to defects \u2014 with the real case that bought each rule, the template for the\n",(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"})," entry and the one for the final report, including how to declare what you did ",(0,t.jsx)(n.strong,{children:"not"}),"\nverify. It works for agents and for people."]}),"\n",(0,t.jsx)(n.h2,{id:"what-a-pr-needs",children:"What a PR needs"}),"\n",(0,t.jsx)(n.h3,{id:"1-new-invariant",children:"1. A new invariant \u2014 or the reason it doesn't need one"}),"\n",(0,t.jsxs)(n.p,{children:["This is the rule that defines the project. Every PR that changes ",(0,t.jsx)(n.strong,{children:"observable behavior"})," brings\none of two things:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"A new invariant"})," in ",(0,t.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),", with a ceiling that has provenance\n(reference file + measured pixel + script that reproduces it), or"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"One sentence in the PR description"}),' saying why it does not need one. Valid reasons: "it is\nalready covered by invariant X" (say which), "it is a refactor with no observable change \u2014 the\ngate gives the same score before and after" (paste both), "it is pure content (text,\nasset) with no game rule attached".']}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:'Invalid reason: "I tested it manually and it looked good".'}),"\n",(0,t.jsxs)(n.p,{children:["Why: ",(0,t.jsx)(n.strong,{children:"intent that does not become an invariant gets optimized away"}),". One round took the\ngate from 16/21 to 19/21 without loosening a single ceiling, and was rejected, because it\nsilently destroyed an aesthetic decision that no invariant encoded. Full case in\n",(0,t.jsx)(n.a,{href:"/docs/en/quality-gates#law-1",children:"The gate"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"2-gate-cannot-get-worse",children:"2. The gate cannot get worse"}),"\n",(0,t.jsxs)(n.p,{children:["Paste the output of ",(0,t.jsx)(n.code,{children:"node tools/eval/invariants.mjs"})," before and after. If any critical one went\nred, the PR does not go in. If you ",(0,t.jsx)(n.strong,{children:"fixed"})," a red one, say which and show it."]}),"\n",(0,t.jsxs)(n.p,{children:["The gate is red today (",(0,t.jsx)(n.a,{href:"./status",children:"see which ones"}),", and the living list with root causes is\nin ",(0,t.jsx)(n.code,{children:"KNOWN-BUGS.md"}),"). That is not a license to make it worse: the commitment is ",(0,t.jsx)(n.em,{children:'"your change\nadds no red"'}),"."]}),"\n",(0,t.jsxs)(n.h3,{id:"3-numbers-with-file-line",children:["3. Numbers, with ",(0,t.jsx)(n.code,{children:"arquivo:linha"})]}),"\n",(0,t.jsxs)(n.p,{children:['The claim "I improved the lighting" is not reviewable. "The ',(0,t.jsx)(n.code,{children:"praca_poderes"})," floor was 8 points\nof L* above the walls, cause at ",(0,t.jsx)(n.code,{children:"map_brasilia.js:NNN"}),', fixed to X" is. This\nrequirement is not style \u2014 it is what allows the next round to check your work.']}),"\n",(0,t.jsx)(n.h3,{id:"4-one-front-per-pr",children:"4. One front per PR"}),"\n",(0,t.jsxs)(n.p,{children:["Consult the conflict table (",(0,t.jsx)(n.a,{href:"/docs/en/architecture#conflict-table",children:"Architecture"}),"). A\nPR that touches weapons + UI + map is three hidden PRs, and will collide with three fronts. In\n",(0,t.jsx)(n.code,{children:"game.js"}),", edit by section; never overwrite the whole file."]}),"\n",(0,t.jsx)(n.h3,{id:"5-repo-hygiene",children:"5. Repository hygiene"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"node --check"})," on every file in ",(0,t.jsx)(n.code,{children:"public/js/"})," that you edited (the CI does this first,\n",(0,t.jsx)(n.code,{children:".github/workflows/ci.yml:19-20"}),")."]}),"\n",(0,t.jsxs)(n.li,{children:["Comments ",(0,t.jsx)(n.strong,{children:"in Portuguese explaining the why"}),", not the what. It is the repo's culture and it is\nwhat survives the next handoff."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Never delete a provenance comment"})," in a cleanup PR. That long paragraph\nexplaining where the number 0,513 came from is what keeps the next round from repeating three\nlost days."]}),"\n",(0,t.jsxs)(n.li,{children:["Touched ",(0,t.jsx)(n.code,{children:"public/js/*.js"}),"? ",(0,t.jsxs)(n.strong,{children:["Bump the ",(0,t.jsx)(n.code,{children:"?v="})," on both sides"]})," \u2014 ",(0,t.jsx)(n.code,{children:"public/js/version.js"})," and the\nimport map of ",(0,t.jsx)(n.code,{children:"src/pages/index.astro"}),'. It has already cost days of "fixes that never arrived".']}),"\n",(0,t.jsxs)(n.li,{children:["Touched ",(0,t.jsx)(n.code,{children:"public/js/*.js"}),", ",(0,t.jsx)(n.code,{children:"maps.js"}),", ",(0,t.jsx)(n.code,{children:"characters.js"}),", or a dependency?\n",(0,t.jsxs)(n.strong,{children:["Run ",(0,t.jsx)(n.code,{children:"npm run docs"})]})," and commit it along. ",(0,t.jsx)(n.code,{children:"docs:check"})," is in ",(0,t.jsx)(n.code,{children:"check:fast"})," and will\nfail \u2014 it takes less than a second and is what keeps the doc from lying again."]}),"\n",(0,t.jsxs)(n.li,{children:["No copyrighted assets. No committed ",(0,t.jsx)(n.code,{children:"service_role"})," key."]}),"\n",(0,t.jsx)(n.li,{children:"No runtime dependency in the game. Three.js is vendored; the game has to run by\ndragging the folder onto a static host."}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"6-editorial-line",children:"6. Editorial line"}),"\n",(0,t.jsxs)(n.p,{children:["From ",(0,t.jsx)(n.code,{children:"CONTRIBUTING.md:7-16"}),": the game has no political side (both teams have the same\nmechanics), does not incite hatred, does not use real people \u2014 only original archetypes, no gore.\nContributions that violate this are rejected. It is not bureaucracy: it is what protects the project\nfrom takedowns and from becoming something else."]}),"\n",(0,t.jsx)(n.h2,{id:"how-to-add-a-weapon",children:"How to add a weapon"}),"\n",(0,t.jsxs)(n.p,{children:["The pipeline is data-driven from the GLB. Weapon GLBs live in ",(0,t.jsx)(n.code,{children:"public/models/weapons/"}),"\n(the count is in the generated block of ",(0,t.jsx)(n.a,{href:"/docs/en/",children:"Getting started"}),")."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Put the GLB"})," at ",(0,t.jsx)(n.code,{children:"public/models/weapons/ui-check.mjs",id:"mutation-first-class",level:3},{value:"How to write an invariant",id:"how-to-write-an-invariant",level:2},{value:"Anti-patterns that have already cost dearly here",id:"anti-patterns",level:3},{value:"This page is the doctrine. The step-by-step is a skill",id:"doctrine-vs-skill",level:2},{value:"Running the gate",id:"running-the-gate",level:2}];function c(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:["\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"the-gate-invariants-provenance-and-mutation",children:"The gate: invariants, provenance and mutation"})}),"\n",(0,i.jsxs)(n.p,{children:["The gate of this repository is a file: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),". It runs in pure node\nand exits with code 1 if any ",(0,i.jsx)(n.strong,{children:"critical"})," invariant fails. It is what CI executes on every\nPR (",(0,i.jsx)(n.code,{children:".github/workflows/ci.yml"}),")."]}),"\n","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),": ",(0,i.jsx)(n.strong,{children:"2,275 lines"}),", ",(0,i.jsx)(n.strong,{children:"65 declared invariant identifiers"}),", with ",(0,i.jsx)(n.strong,{children:"28"})," declared ",(0,i.jsx)(n.code,{children:"skip()"})," paths."]}),"\n",(0,i.jsxs)(n.li,{children:["The harness contains ",(0,i.jsx)(n.strong,{children:"192 scripts"})," in ",(0,i.jsx)(n.code,{children:"tools/eval/"}),", plus ",(0,i.jsx)(n.strong,{children:"54 pipeline scripts"})," in ",(0,i.jsx)(n.code,{children:"tools/"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The number of critical checks in one run depends on the inputs present on that machine; dated results belong in ",(0,i.jsx)(n.code,{children:"KNOWN-BUGS.md"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\ngrep -o \"skip('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.p,{children:["The third item above is the distinction that most confuses newcomers: ",(0,i.jsx)(n.strong,{children:"declared\nidentifier \u2260 evaluated invariant."})," Several become ",(0,i.jsx)(n.code,{children:"skip"})," instead of ",(0,i.jsx)(n.code,{children:"put"})," when their\ninput is missing (the viewmodel auditor's JSON, a GLB, a folder of anims). ",(0,i.jsx)(n.code,{children:"skip"})," is a\n",(0,i.jsx)(n.strong,{children:"green gate by absence of data"}),', and that is why it always carries the reason. See\n"Severity", below.']}),"\n",(0,i.jsx)(n.p,{children:"This page is the most useful one on the site. If you only read one, read this one."}),"\n",(0,i.jsx)(n.h2,{id:"why-it-exists",children:"Why it exists"}),"\n",(0,i.jsxs)(n.p,{children:["From the header of the file itself, ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:5-19"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The owner spent 3 days in a cycle where every round fixed one thing and broke\nanother, and we only found out one round later. The cause wasn't lack of care:\nit was the lack of a RULER (quality gate). A critic (human or agent) judges screenshots;\nconsistency and flow are properties of the game ",(0,i.jsx)(n.strong,{children:"IN MOTION"}),", and almost every defect he\nreported is not taste \u2014 it's a violated invariant."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"And the translation, which is the most important thing in this entire repository:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"What the owner said"}),(0,i.jsx)(n.th,{children:"Which invariant it became"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the hands are floating in the air"'}),(0,i.jsx)(n.td,{children:"hand\u2194grip distance has a ceiling"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the weapon points downward"'}),(0,i.jsx)(n.td,{children:"the barrel has a maximum angle"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"in ADS I can\'t see the weapon or the crosshair"'}),(0,i.jsx)(n.td,{children:"the weapon has a minimum and maximum area"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"sniper with no zoom"'}),(0,i.jsx)(n.td,{children:"aiming FOV < hip FOV"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"several weapons look the same"'}),(0,i.jsx)(n.td,{children:"silhouettes must differ"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the bot shoots out of nowhere"'}),(0,i.jsx)(n.td,{children:"damage requires prior LOS"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"there are 2 of them eliminating me"'}),(0,i.jsx)(n.td,{children:"1 killfeed per death"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:20-21"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"GOLDEN RULE: nothing gets committed with a RED invariant. And every new bug the owner\nreports becomes an invariant here \u2014 that's how it never comes back."})}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-an-invariant-is-here",children:"What an invariant is here"}),"\n",(0,i.jsxs)(n.p,{children:["An invariant is a ",(0,i.jsx)(n.strong,{children:"property of the game that can be measured without a human watching"}),",\nwith a ceiling or a range that has provenance. It is not a unit test: almost no invariant\ntests a function. They measure the ",(0,i.jsx)(n.strong,{children:"state of the game actually running"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Three forms, all present in the file:"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"1. Read from the source code."})," Cheap, runs in milliseconds, catches whole classes of bug.\nReal example, quoted verbatim from ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1439-1446"})," \u2014 the file's\ncomments are written in Portuguese:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// ARM1 \u2014 toda arma com luneta precisa de zoom de verdade. \"Snipers sem zoom\"\n// \xe9 reclama\xe7\xe3o literal; a solu\xe7\xe3o N\xc3O \xe9 tirar a luneta, \xe9 fazer a certa.\nconst bloco = gsrc.slice(0, gsrc.indexOf('};', gsrc.indexOf('const WEAPONS')) + 2);\nconst linhas = bloco.split('\\n').filter((l) => /^\\s*\\w+:\\s*\\{/.test(l));\nconst semZoom = linhas.filter((l) => /scope:\\s*true/.test(l) && !/spreadScope/.test(l))\n .map((l) => l.trim().split(':')[0]);\nput('ARM1', 'toda arma com scope:true declara spreadScope', semZoom.length === 0,\n semZoom.length ? semZoom.join(', ') : `${linhas.length} armas conferidas`);\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"2. Measured in the real game running in node."})," ",(0,i.jsx)(n.code,{children:"tools/eval/harness.mjs"})," boots the real\n",(0,i.jsx)(n.code,{children:"Game"})," class, with the real maps, with DOM/canvas stubbed. It is the ",(0,i.jsx)(n.strong,{children:"production code"}),"\nthat gets measured, not a reimplementation \u2014 ",(0,i.jsx)(n.code,{children:"tools/eval/botsim.mjs:8-9"}),": ",(0,i.jsx)(n.em,{children:'"if the number\nimproves here, it improved in the game"'}),". This is where BOT1\u2013BOT8, MAP1\u2013MAP3, CTF1, MAT1/MAT2,\nFOG1, TEX1, VM14, MOD1/MOD2 come from."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"3. Measured on the geometry of the assets."})," ",(0,i.jsx)(n.code,{children:"vm-mint-audit.mjs"})," opens every weapon GLB with\nits own GLB parser and projects the viewmodel onto the screen. This is where VM1\u2013VM19 come from."]}),"\n",(0,i.jsxs)(n.p,{children:["What does ",(0,i.jsx)(n.strong,{children:"not"})," belong here: an invariant that requires browser pixels. Those are marked\n",(0,i.jsx)(n.code,{children:"browser"})," and are skipped, with the reason stated \u2014 SwiftShader costs ~4 min per map load\non this machine (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:99"}),")."]}),"\n",(0,i.jsx)(n.h3,{id:"severity",children:"Severity"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"put(id, desc, ok, evid, sev)"})," accepts ",(0,i.jsx)(n.code,{children:"'crit'"})," (the default) or ",(0,i.jsx)(n.code,{children:"'warn'"}),"\n(",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:81-82"}),"). A red critical fails the PR. Warn is measured noise that\nsomeone needs to look at but doesn't block \u2014 it's where BOT1/BOT2/BOT3/BOT6/BOT7, ARM4 and\nARM5 live. ",(0,i.jsx)(n.code,{children:"skip()"})," is the third state, and it is ",(0,i.jsx)(n.strong,{children:"dangerous"}),": a green gate by absence of\ndata. That is why every ",(0,i.jsx)(n.code,{children:"skip"})," carries the reason."]}),"\n",(0,i.jsx)(n.h2,{id:"the-two-house-laws",children:"The two house laws"}),"\n",(0,i.jsx)(n.h3,{id:"law-1",children:"Law 1 \u2014 Intention that doesn't become an invariant is optimized away"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:452-461"}),"."]})," The case, verbatim:"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["the previous round took the gate from ",(0,i.jsx)(n.strong,{children:"16/21 to 19/21 without loosening a single ceiling"})," and\nwas still ",(0,i.jsx)(n.strong,{children:"REJECTED"})," by the owner, because to close VM5/VM10 it ",(0,i.jsxs)(n.strong,{children:["ZEROED the\n",(0,i.jsx)(n.code,{children:"VM_OFF"})," y"]}),' and silently changed the look. No invariant encoded "where the\nmuzzle sits", so the metric was optimized and the INTENT was destroyed. Goodhart\'s\nlaw, in full. ',(0,i.jsx)(n.strong,{children:"INTENTION THAT DOESN'T BECOME AN INVARIANT IS OPTIMIZED AWAY."})]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Read again what happened, because it is counterintuitive: the agent ",(0,i.jsx)(n.strong,{children:"did not cheat"}),". It\ndidn't loosen any ceiling. It genuinely raised the score. And the result was worse, because\n",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," is the term that ",(0,i.jsx)(n.strong,{children:"dominates the weapon's position on screen"})," \u2014 ",(0,i.jsx)(n.code,{children:"public/js/game.js:555"}),"\ndeclares ",(0,i.jsx)(n.code,{children:"VM_OFF = [0.03, -0.1000, 0]"}),", and ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1163"})," measures the\nsensitivity: ",(0,i.jsx)(n.em,{children:'"removing recuoZ moves the grip 3,5 cm; removing VM_OFF moves it 23 cm"'}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Zeroing that term closed two invariants and erased the aesthetic decision the owner had\nmade \u2014 which wasn't written anywhere the ruler could read."}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"The fix was not punishing the agent. It was writing the intention as an invariant."})," Today\nthere is VM12 (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:497"}),"): ",(0,i.jsx)(n.em,{children:'"CS 1.6 look: muzzle RIGHT below\nthe crosshair (y between 0,50 and 0,62) in the 2 aspects"'}),". With it in place, the same\noptimization goes ",(0,i.jsx)(n.strong,{children:"red"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"And the operational consequence, from the same comment:"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Whoever wants to change the look has to change ",(0,i.jsx)(n.strong,{children:"THIS ceiling"})," explicitly, in a diff the\nowner sees, instead of touching ",(0,i.jsx)(n.code,{children:"VM_OFF"}),' and reporting "+3 invariants".']}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"What this means for your PR",type:"tip",children:(0,i.jsxs)(n.p,{children:["If your change improves the gate's score, the first question is: ",(0,i.jsx)(n.strong,{children:"what did I change that\nthe gate doesn't look at?"}),' If the answer is "the look", "the feel" or "the vibe", write the\ninvariant before sending the PR \u2014 or explain in the PR why it doesn\'t fit.']})}),"\n",(0,i.jsx)(n.h3,{id:"law-2",children:"Law 2 \u2014 A ceiling without provenance is an opinion"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py:1-40"}),"."]})," That docstring is the house doctrine. The case:"]}),"\n",(0,i.jsxs)(n.p,{children:["For ",(0,i.jsx)(n.strong,{children:"three days"})," the weapons gate was solved against ",(0,i.jsx)(n.strong,{children:"asserted"})," numbers:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["VM12 required ",(0,i.jsx)(n.em,{children:'"muzzle at y \u2265 0,66"'}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsx)(n.code,{children:"vmattach.js"})," doc said ",(0,i.jsx)(n.em,{children:'"ENTIRE stock in the corner"'}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Neither of the two was measured on any image. According to ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:461-463"}),",\nthe 0,66 floor came from a comment in ",(0,i.jsx)(n.code,{children:"public/js/vmattach.js"})," \u2014 ",(0,i.jsx)(n.em,{children:'"the muzzle sits at ~0,66H"'})," \u2014\nwhich in turn came from a video someone watched. (The gate's comment points to\n",(0,i.jsx)(n.code,{children:"vmattach.js:387-392"}),"; today the text is at ",(0,i.jsx)(n.code,{children:"vmattach.js:395"}),", because the file moved. That is\nexactly why ",(0,i.jsx)(n.code,{children:"ARCH.md"})," is generated \u2014 see ",(0,i.jsx)(n.a,{href:"/docs/en/architecture",children:"Architecture"}),".)"]}),"\n",(0,i.jsxs)(n.p,{children:["The owner looked at the result and said, verbatim (",(0,i.jsx)(n.code,{children:"ref-measure.py:14-17"}),"):"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:'"it looks different from CS 1.6 and Quake and UT; in those 3 the weapon is always in the\nbottom-right corner and the stock is always OUTSIDE; after 3 days and an entire folder of\nreference neither you nor Kimi understood that."'}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Then the frames were ",(0,i.jsx)(n.strong,{children:"measured"}),". ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py"})," does color segmentation on the\nbottom-right quadrant, takes the largest connected component, and writes\n",(0,i.jsx)(n.code,{children:"tools/eval/ref_viewmodel.json"}),". Result:"]}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Frame"}),(0,i.jsx)(n.th,{children:"Muzzle (x, y)"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Screen area"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Axis angle"}),(0,i.jsx)(n.th,{children:"Crosses the right edge?"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"cs16_ak_dust.jpg"})}),(0,i.jsxs)(n.td,{children:["0,564 ; ",(0,i.jsx)(n.strong,{children:"0,513"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"9,76%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"28,0\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"cs16_m4_dust.jpg"})}),(0,i.jsxs)(n.td,{children:["0,569 ; ",(0,i.jsx)(n.strong,{children:"0,598"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"9,78%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"34,8\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"valorant_vandal.jpg"})}),(0,i.jsxs)(n.td,{children:["0,648 ; ",(0,i.jsx)(n.strong,{children:"0,587"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"13,09%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"4,6\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]})]})]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Both asserted numbers were wrong:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The CS 1.6 muzzle sits at ",(0,i.jsx)(n.strong,{children:"0,513\u20130,598"})," \u2014 right below the crosshair (0,5), 1 to 10\npercentage points below center. Not at 0,66\u20130,93. The wrong floor was keeping our\nweapon ",(0,i.jsx)(n.strong,{children:"sunk"})," at 0,667\u20130,816 (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:472-475"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:["The stock ",(0,i.jsx)(n.strong,{children:"EXITS through the corner"})," in all 3 frames. Exiting is the standard, not the defect\n(",(0,i.jsx)(n.code,{children:"ref_viewmodel.json"})," \u2192 ",(0,i.jsx)(n.code,{children:"faixas.cruzaBordaDireita: true"})," in all 3)."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the collateral damage: with the false ceiling, the previous round's solver ",(0,i.jsx)(n.em,{children:'"proved"'})," that 3%\nof area was infeasible. The proof was correct ",(0,i.jsx)(n.strong,{children:"against that ceiling"})," \u2014 and it was the ceiling that\nwas false (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:476-478"}),")."]}),"\n",(0,i.jsxs)(n.p,{children:["The rule that remained, ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py:21-22"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"AN INVARIANT CEILING ONLY GETS IN WITH PROVENANCE \u2014 reference file, measured pixel, and\nthis script reproducing the number. A number without an image is an opinion."})}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Today the framing invariants carry the provenance in their own text: VM1 (range\n0,50\u20130,60, ref 0,520\u20130,565), VM3 (22\u201342\xb0, ref 28,0\xb0 and 34,8\xb0), VM5 (6\u201316%, ref\n9,76\u201313,09%), VM12 (0,50\u20130,62, ref 0,513\u20130,598), VM16 (slice at the right edge 0,02\u20130,20,\nref 0,053\u20130,095)."}),"\n",(0,i.jsx)(n.admonition,{title:"Provenance includes admitting what the image does NOT measure",type:"note",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:599-602"}),' refuses to create a ceiling for "how much of the weapon\nsits outside the frame", because what is outside is invisible in the photo \u2014 there is no way to\nknow whether the AK\'s stock ends 5 cm or 50 cm past the edge. The numbers remain in the JSON as\n',(0,i.jsx)(n.strong,{children:"evidence, without a gate"}),". That is provenance taken seriously: the ruler says where it stops\nknowing."]})}),"\n",(0,i.jsxs)(n.p,{children:["And the same rigor bites whoever wrote the ruler, in the most uncomfortable case possible: ",(0,i.jsx)(n.strong,{children:"the\ncharacter reference photos arrived, were measured, and were REJECTED by the ruler\nitself."})," ",(0,i.jsx)(n.code,{children:"tools/eval/char-probe.mjs:25-45"})," tells the whole episode \u2014 ",(0,i.jsx)(n.code,{children:"references/funkeiros/"}),"\nhas 23 files and ",(0,i.jsx)(n.code,{children:"references/palhacos/"})," has 21, all run through ",(0,i.jsx)(n.code,{children:"ref-body.py"}),", with the\nmasks ",(0,i.jsx)(n.strong,{children:"looked at"})," (",(0,i.jsx)(n.code,{children:"--masks"}),"). The verdict, said to our face by the comment itself: they are\nselfies and close-ups; the heuristic segmentation returns the hand, a piece of jacket or the hair\nof someone else in the background, and the shoulder/height ratio comes out between ",(0,i.jsx)(n.strong,{children:"0,42 and 3,78"})," when a human\nmeasures 0,259. About ~1 usable full-body photo remains \u2014 that is not a sample."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"ref-body.py"})," requires ",(0,i.jsx)(n.strong,{children:"6 accepted photos"})," for a ceiling to become measured, and it ",(0,i.jsx)(n.strong,{children:"says why\nit didn't"}),". So the absolute ceiling of CHR1 remains a ",(0,i.jsx)(n.strong,{children:"published fallback"})," (Drillis &\nContini 1966, via Winter), declared as such in the ",(0,i.jsx)(n.code,{children:"procedencia"})," field of the JSON and in the\nreport column."]}),"\n",(0,i.jsxs)(n.p,{children:["Notice what that means: having the photo is ",(0,i.jsx)(n.strong,{children:"not"})," having the measurement. It was easier to accept\nthat the data was bad than to promote a fragile measurement to a ceiling \u2014 and that is Law 2\napplied against the interest of whoever wrote the ruler."]}),"\n",(0,i.jsxs)(n.admonition,{type:"warning",children:[(0,i.jsxs)(n.mdxAdmonitionTitle,{children:[(0,i.jsx)(n.code,{children:"references/"})," does NOT come with the clone \u2014 and that is a decision, not carelessness"]}),(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"git ls-files references"})," returns ",(0,i.jsx)(n.strong,{children:"zero"}),". On 04/08/2026 the entire folder was\nuntracked by the owner's decision (\"references can stay local because we're going to build\nlocally\"): they are the UI target screens and the viewmodel reference frames, and they live only on\nhis machine."]}),(0,i.jsxs)(n.p,{children:["What ",(0,i.jsx)(n.strong,{children:"survives the clone are the NUMBERS measured from them"}),": ",(0,i.jsx)(n.code,{children:"tools/eval/ref_ui.json"})," and\n",(0,i.jsx)(n.code,{children:"tools/eval/ref_viewmodel.json"})," are versioned. That is the contract \u2014 if a ruler of yours\nneeds to run in CI, it reads the JSON, never the PNG. A ruler that opens an image from\n",(0,i.jsx)(n.code,{children:"references/"})," goes red on every machine that isn't the owner's, and red-by-environment is\nthe worst kind: it teaches whoever works here to ignore red."]})]}),"\n",(0,i.jsx)(n.h2,{id:"mutation-test",children:"Mutation test of the ruler itself"}),"\n",(0,i.jsx)(n.p,{children:"This is the part almost no project has, and it is where this repository is genuinely\ndifferent."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"A gate that doesn't move when you break the code on purpose is blind."})}),"\n",(0,i.jsxs)(n.p,{children:["The way to find that out is to mutate: take the fixed code, ",(0,i.jsx)(n.strong,{children:"undo the fix on\npurpose"}),", run the gate, and see whether it goes red. If it stays green, the gate is not\nmeasuring what you think it measures."]}),"\n",(0,i.jsx)(n.h3,{id:"the-case-20-22-green",children:"The case: 20/22 green with the fix removed"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:910-920"}),"."]})}),"\n",(0,i.jsxs)(n.p,{children:["The context: ",(0,i.jsx)(n.code,{children:"public/js/game.js:577"})," declares"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"const vmOffY = (aspect) => VM_OFF[1] * ((16 / 9) / (aspect || 16 / 9));\n"})}),"\n",(0,i.jsxs)(n.p,{children:["It is the per-aspect vertical framing fix \u2014 the reason the weapon sits in the same\nplace in 16:9 and in 3:2 (the owner plays in 3:2). It is ",(0,i.jsx)(n.strong,{children:"called"})," in the Y argument of\n",(0,i.jsx)(n.code,{children:"this.vm.root.position.set(...)"}),", at ",(0,i.jsx)(n.code,{children:"public/js/game.js:4873"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"The hole, measured in 08/2026:"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["the ",(0,i.jsx)(n.code,{children:"vmOff"})," step only checked ",(0,i.jsx)(n.code,{children:"/this\\.vm\\.root\\.position\\.set\\(\\s*VM_OFF\\[0\\]/"})," \u2014 the X\nterm. The Y term was checked by no one, and the auditor (",(0,i.jsx)(n.code,{children:"vm-mint-audit.mjs:196"}),",\n",(0,i.jsx)(n.code,{children:"loadOffYFn"}),") reads the ",(0,i.jsx)(n.strong,{children:"DECLARATION"})," ",(0,i.jsx)(n.code,{children:"const vmOffY = (aspect) => ..."})," by regex ",(0,i.jsx)(n.strong,{children:"without ever\nasking whether anyone CALLS it"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["Result: swapping in ",(0,i.jsx)(n.code,{children:"game.js"})," the call ",(0,i.jsx)(n.code,{children:"vmOffY(...)"})," for ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," in the Y argument\n\u2014 that is, ",(0,i.jsx)(n.strong,{children:"removing the per-aspect vertical framing fix entirely"})," \u2014 the\nwhole gate stayed ",(0,i.jsx)(n.strong,{children:"GREEN (20/22, with VM9, VM10, VM12 and VM15 all green)"}),". A\ngate that cannot tell the fixed build from the build without the fix is measuring\nnothing."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Notice the mechanism of the error, because it repeats in any language: ",(0,i.jsxs)(n.strong,{children:["the invariant\nwas reading the ",(0,i.jsx)(n.em,{children:"declaration"})," of a constant, and not the ",(0,i.jsx)(n.em,{children:"use"}),"."]})," Declaring and not calling is the\ncheapest way for a fix to vanish with the gate green."]}),"\n",(0,i.jsxs)(n.p,{children:["The repair was surgical and worth copying. AUD1 today separates the three arguments of\n",(0,i.jsx)(n.code,{children:"position.set(...)"})," with a ",(0,i.jsx)(n.strong,{children:"parenthesis scanner"})," \u2014 not ",(0,i.jsx)(n.code,{children:"split(',')"}),", which would cut\ninside the function call \u2014 and requires ",(0,i.jsx)(n.strong,{children:"by name"})," that the Y argument calls ",(0,i.jsx)(n.code,{children:"vmOffY("}),".\nAnd it closes the other path along with it (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1148-1151"}),"): the formula of\n",(0,i.jsx)(n.code,{children:"vmOffY"})," is ",(0,i.jsxs)(n.strong,{children:["read from ",(0,i.jsx)(n.code,{children:"game.js"})," and evaluated"]})," at 16/9, and it has to yield exactly ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The two checks together cover the two ways for the fix to vanish: ",(0,i.jsx)(n.strong,{children:"deleting the CALL"}),"\n(mutation measured in 08/2026) or ",(0,i.jsx)(n.strong,{children:"tampering with the FORMULA"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"not-an-isolated-case",children:"It was not an isolated case \u2014 there were three"}),"\n",(0,i.jsx)(n.p,{children:"The same hole showed up in two other places, and each one became a new AUD1 step:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Mutation"}),(0,i.jsx)(n.th,{children:"Score with the fix undone"}),(0,i.jsx)(n.th,{children:"Cause of the false green"}),(0,i.jsx)(n.th,{children:"Where"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Swap ",(0,i.jsx)(n.code,{children:"vmOffY(...)"})," for ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," in the Y argument"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"20/22 green"})}),(0,i.jsx)(n.td,{children:"the invariant read the declaration, not the use"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:910-920"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Swap ",(0,i.jsx)(n.code,{children:"g.rotation.set(pit, yaw, t.roll)"})," for ",(0,i.jsx)(n.code,{children:"g.rotation.set(0, 0, t.roll)"})]}),(0,i.jsx)(n.td,{children:"green"}),(0,i.jsxs)(n.td,{children:["the ",(0,i.jsx)(n.code,{children:"VM_FRAME.cls"})," table still holds the angles, and the three mirrors still match ",(0,i.jsx)(n.strong,{children:"each other"})]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:932-944"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Delete ",(0,i.jsx)(n.code,{children:"* (weaponCFG(id).vm ?? 1)"})," from the mesh scale"]}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.strong,{children:"28/37 green, AUD1 included"}),' ("worst \u0394scale 0.0004")']}),(0,i.jsxs)(n.td,{children:["both ends read ",(0,i.jsx)(n.code,{children:"vm"})," from ",(0,i.jsx)(n.code,{children:"weapons.js"}),"; ",(0,i.jsxs)(n.strong,{children:[(0,i.jsx)(n.code,{children:"game.js"})," is never asked"]})," \u2014 it was the auditor checking itself"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:971-975"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Mutate ",(0,i.jsx)(n.code,{children:"this._adsPose['pistol']"})]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"20/22 green"})}),(0,i.jsx)(n.td,{children:"ADS had no invariant at all"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:1185"})})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"The common pattern of the four is the same, and it is what you should look for in your invariant:"}),"\n",(0,i.jsx)(n.admonition,{title:"The false-green pattern",type:"danger",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"The ruler is checking a copy of the rule instead of the game."})," Whether because it reads the declaration\nand not the use, or because it compares two mirrors that read the same source, or because the\nparameter table stays correct while no one applies it. If the two ends of your\ncomparison can stay consistent ",(0,i.jsx)(n.strong,{children:"without going through the production code"}),", your\ninvariant is blind."]})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/mat-check.mjs:18-27"})," solves this in the most direct way possible: the body of\n",(0,i.jsx)(n.code,{children:"fixVmMaterials"})," is ",(0,i.jsxs)(n.strong,{children:["cut out of ",(0,i.jsx)(n.code,{children:"game.js"})," and executed"]})," against a probe material. If the\ncode changes, the ruler changes with it. ",(0,i.jsx)(n.em,{children:'"a ruler that carries a COPY of the rule lies on the day\nthe rule changes."'})]}),"\n",(0,i.jsxs)(n.h3,{id:"mutation-first-class",children:["Mutation as a first-class thing: ",(0,i.jsx)(n.code,{children:"ui-check.mjs"})]}),"\n",(0,i.jsxs)(n.p,{children:["The UI harness has a ",(0,i.jsx)(n.strong,{children:"versioned mutation table"}),", and each one declares which gate\nhas to go red. ",(0,i.jsx)(n.code,{children:"tools/eval/ui-check.mjs:1046-1050"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Each mutation UNDOES one of this round's fixes (or punches through a gate on purpose) and says\nwhich gate MUST go red. ",(0,i.jsx)(n.strong,{children:"A ruler that does not fail the previous version of\nits own file is not a ruler, it is decoration."})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Running one:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # expects UI1 to go RED\nMUT=ui3_prompt_na_mira node tools/eval/ui-check.mjs # expects UI3 to go RED\nMUT=ui2_prompt_eterno node tools/eval/ui-check.mjs # expects UI2 to go RED\nMUT=ui4_ctf_sem_relogio node tools/eval/ui-check.mjs # expects UI4 to go RED\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The 7 mutations are in ",(0,i.jsx)(n.code,{children:"tools/eval/ui-check.mjs:1051-1134"}),". Two mechanics: ",(0,i.jsx)(n.code,{children:"css"})," rewrites\n",(0,i.jsx)(n.code,{children:"public/style.css"})," ",(0,i.jsx)(n.strong,{children:"read in memory"})," (never on disk \u2014 other agents are editing the\nfile right now), and ",(0,i.jsx)(n.code,{children:"sim"})," monkey-patches the already-booted ",(0,i.jsx)(n.code,{children:"Game"})," object. If the ",(0,i.jsx)(n.code,{children:"css"})," mutation\nmatches nothing, the script exits with code 2 saying ",(0,i.jsx)(n.em,{children:'"the CSS changed shape"'})," \u2014 because a\nmutation that doesn't apply is also a false green (",(0,i.jsx)(n.code,{children:"ui-check.mjs:1164"}),")."]}),"\n",(0,i.jsx)(n.h2,{id:"how-to-write-an-invariant",children:"How to write an invariant"}),"\n",(0,i.jsx)(n.p,{children:"Checklist, in order:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Start from the defect's sentence."})," Verbatim, with the words of whoever complained. Every\nharness in this codebase starts that way, and it is not style: it is what keeps the invariant from\nmeasuring something else. See the header of ",(0,i.jsx)(n.code,{children:"tools/eval/map-check.mjs:5-12"})," \u2014 five sentences from the owner,\nfive invariants."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Translate it into a measurable quantity."}),' "the players are SUBMERGED UNDER THE\nSTATUE" \u2192 ',(0,i.jsx)(n.em,{children:"there is visible map geometry whose top rises more than 0,30 m above the floor\nlocal to that point"})," (MAP1). Note that the operational definition includes ",(0,i.jsx)(n.strong,{children:"why 0,30 m"}),':\nit is the step the body climbs; above that it is not "stepping over", it is "being inside".']}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Find the ceiling's provenance."})," Reference file + measured pixel + script that\nreproduces it. If it doesn't exist, ",(0,i.jsx)(n.strong,{children:"say it is a fallback"})," and cite the published source, as C1\ndoes. Never invent the number."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Measure the production code, not a copy of it."})," Import the real module, cut the\nfunction out of the file and execute it, or require the call by name in the source text."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Mutate and confirm it goes red."})," Undo the fix you just made and\nrun the gate. If it stays green, your invariant is blind \u2014 go back to step 4. If it can\nbe automated, register the mutation in a table, as ",(0,i.jsx)(n.code,{children:"ui-check.mjs"})," does."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Write the evidence, not just the boolean."})," The fourth argument of ",(0,i.jsx)(n.code,{children:"put()"})," is what\nsomeone will read three months from now: ",(0,i.jsx)(n.code,{children:'"0,504 a 0,619 da altura em 52 medidas | 0 fora da faixa"'})," is useful; ",(0,i.jsx)(n.code,{children:'"ok"'})," is not."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Write the provenance comment above it."})," In Portuguese, saying what\nhappened when the number was wrong. That comment is what keeps the next round\nfrom redoing the mistake."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"anti-patterns",children:"Anti-patterns that have already cost dearly here"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Anti-pattern"}),(0,i.jsx)(n.th,{children:"What it produced"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Reading a constant's declaration instead of its use"}),(0,i.jsx)(n.td,{children:"20/22 green with the fix removed"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Two mirrors that read the same source"}),(0,i.jsx)(n.td,{children:'28/37 green, "worst \u0394scale 0.0004", with the knob turned off'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Format adapter silently broken"}),(0,i.jsxs)(n.td,{children:["VM1\u2013VM6 have been ",(0,i.jsx)(n.strong,{children:"SKIPPED since the auditor exists"})," \u2014 6 viewmodel invariants that never ran once (",(0,i.jsx)(n.code,{children:"invariants.mjs:121-127"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Measuring the gap against the wrong local floor"}),(0,i.jsxs)(n.td,{children:["a pickup inside the pool reported gap ",(0,i.jsx)(n.strong,{children:"0,0000 \u2014 GREEN"})," (",(0,i.jsx)(n.code,{children:"pickup-check.mjs:20-23"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"waypoint \u2264 3 m" as a reachability proxy'}),(0,i.jsxs)(n.td,{children:["74 false positives and green in a closed pocket (",(0,i.jsx)(n.code,{children:"pickup-check.mjs:34-42"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Floor without ceiling"}),(0,i.jsxs)(n.td,{children:['"muzzle \u2265 0,66" accepts the muzzle at 0,95 (weapon in the basement) \u2014 that is how we got to 0,816 (',(0,i.jsx)(n.code,{children:"invariants.mjs:432-434"}),")"]})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"doctrine-vs-skill",children:"This page is the doctrine. The step-by-step is a skill"}),"\n",(0,i.jsxs)(n.p,{children:["What to do, in order, when someone reports a defect \u2014 reproduce, measure before\nfixing, refute the obvious guess, mutate the ruler, run the gate in the right order and report\nwhat was ",(0,i.jsx)(n.strong,{children:"not"})," verified \u2014 is in ",(0,i.jsx)(n.code,{children:".claude/skills/bug-hunt/SKILL.md"}),", with the real case\nthat paid for each rule. It is written for agents ",(0,i.jsx)(n.strong,{children:"and"})," for people, and it points back to\nthis page instead of repeating it."]}),"\n",(0,i.jsx)(n.h2,{id:"running-the-gate",children:"Running the gate"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"node tools/eval/invariants.mjs # everything that runs without a browser\nnode tools/eval/invariants.mjs --json # machine-readable output\nnpm run check # syntax + gate + vm + recoil + bots\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Current production, data, and debt sources: ",(0,i.jsx)(n.a,{href:"./status",children:"Current state"}),"."]})]})}function l(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>o});var s=t(6540);const i={},r=s.createContext(i);function a(e){const n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
+"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[305],{5185(e,n,t){t.r(n),t.d(n,{assets:()=>h,contentTitle:()=>o,default:()=>l,frontMatter:()=>a,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"quality-gates","title":"The gate: invariants, provenance and mutation","description":"What an invariant is in this repo, how to write one, the two house laws with the real case behind each, and the mutation test of the ruler itself.","source":"@site/i18n/en/docusaurus-plugin-content-docs/current/quality-gates.md","sourceDirName":".","slug":"/quality-gates","permalink":"/docs/en/quality-gates","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/quality-gates.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"id":"quality-gates","title":"The gate: invariants, provenance and mutation","sidebar_label":"The gate (quality gates)","sidebar_position":4,"slug":"/quality-gates","description":"What an invariant is in this repo, how to write one, the two house laws with the real case behind each, and the mutation test of the ruler itself."},"sidebar":"dev","previous":{"title":"AI instrumentation","permalink":"/docs/en/ai-instrumentation"},"next":{"title":"BotBrain","permalink":"/docs/en/botbrain"}}');var i=t(4848),r=t(8453);const a={id:"quality-gates",title:"The gate: invariants, provenance and mutation",sidebar_label:"The gate (quality gates)",sidebar_position:4,slug:"/quality-gates",description:"What an invariant is in this repo, how to write one, the two house laws with the real case behind each, and the mutation test of the ruler itself."},o="The gate: invariants, provenance and mutation",h={},d=[{value:"Why it exists",id:"why-it-exists",level:2},{value:"What an invariant is here",id:"what-an-invariant-is-here",level:2},{value:"Severity",id:"severity",level:3},{value:"The two house laws",id:"the-two-house-laws",level:2},{value:"Law 1 \u2014 Intention that doesn't become an invariant is optimized away",id:"law-1",level:3},{value:"Law 2 \u2014 A ceiling without provenance is an opinion",id:"law-2",level:3},{value:"Mutation test of the ruler itself",id:"mutation-test",level:2},{value:"The case: 20/22 green with the fix removed",id:"the-case-20-22-green",level:3},{value:"It was not an isolated case \u2014 there were three",id:"not-an-isolated-case",level:3},{value:"Mutation as a first-class thing: ui-check.mjs",id:"mutation-first-class",level:3},{value:"How to write an invariant",id:"how-to-write-an-invariant",level:2},{value:"Anti-patterns that have already cost dearly here",id:"anti-patterns",level:3},{value:"This page is the doctrine. The step-by-step is a skill",id:"doctrine-vs-skill",level:2},{value:"Running the gate",id:"running-the-gate",level:2}];function c(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:["\n",(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"the-gate-invariants-provenance-and-mutation",children:"The gate: invariants, provenance and mutation"})}),"\n",(0,i.jsxs)(n.p,{children:["The gate of this repository is a file: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),". It runs in pure node\nand exits with code 1 if any ",(0,i.jsx)(n.strong,{children:"critical"})," invariant fails. It is what CI executes on every\nPR (",(0,i.jsx)(n.code,{children:".github/workflows/ci.yml"}),")."]}),"\n","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs"}),": ",(0,i.jsx)(n.strong,{children:"2,275 lines"}),", ",(0,i.jsx)(n.strong,{children:"65 declared invariant identifiers"}),", with ",(0,i.jsx)(n.strong,{children:"28"})," declared ",(0,i.jsx)(n.code,{children:"skip()"})," paths."]}),"\n",(0,i.jsxs)(n.li,{children:["The harness contains ",(0,i.jsx)(n.strong,{children:"200 scripts"})," in ",(0,i.jsx)(n.code,{children:"tools/eval/"}),", plus ",(0,i.jsx)(n.strong,{children:"54 pipeline scripts"})," in ",(0,i.jsx)(n.code,{children:"tools/"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The number of critical checks in one run depends on the inputs present on that machine; dated results belong in ",(0,i.jsx)(n.code,{children:"KNOWN-BUGS.md"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\ngrep -o \"skip('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Block generated by ",(0,i.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,i.jsx)(n.code,{children:"grep -o \"put('[A-Z0-9_]*'\" tools/eval/invariants.mjs | sort -u | wc -l"})]}),"\n"]}),"\n","\n",(0,i.jsxs)(n.p,{children:["The third item above is the distinction that most confuses newcomers: ",(0,i.jsx)(n.strong,{children:"declared\nidentifier \u2260 evaluated invariant."})," Several become ",(0,i.jsx)(n.code,{children:"skip"})," instead of ",(0,i.jsx)(n.code,{children:"put"})," when their\ninput is missing (the viewmodel auditor's JSON, a GLB, a folder of anims). ",(0,i.jsx)(n.code,{children:"skip"})," is a\n",(0,i.jsx)(n.strong,{children:"green gate by absence of data"}),', and that is why it always carries the reason. See\n"Severity", below.']}),"\n",(0,i.jsx)(n.p,{children:"This page is the most useful one on the site. If you only read one, read this one."}),"\n",(0,i.jsx)(n.h2,{id:"why-it-exists",children:"Why it exists"}),"\n",(0,i.jsxs)(n.p,{children:["From the header of the file itself, ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:5-19"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The owner spent 3 days in a cycle where every round fixed one thing and broke\nanother, and we only found out one round later. The cause wasn't lack of care:\nit was the lack of a RULER (quality gate). A critic (human or agent) judges screenshots;\nconsistency and flow are properties of the game ",(0,i.jsx)(n.strong,{children:"IN MOTION"}),", and almost every defect he\nreported is not taste \u2014 it's a violated invariant."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"And the translation, which is the most important thing in this entire repository:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"What the owner said"}),(0,i.jsx)(n.th,{children:"Which invariant it became"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the hands are floating in the air"'}),(0,i.jsx)(n.td,{children:"hand\u2194grip distance has a ceiling"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the weapon points downward"'}),(0,i.jsx)(n.td,{children:"the barrel has a maximum angle"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"in ADS I can\'t see the weapon or the crosshair"'}),(0,i.jsx)(n.td,{children:"the weapon has a minimum and maximum area"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"sniper with no zoom"'}),(0,i.jsx)(n.td,{children:"aiming FOV < hip FOV"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"several weapons look the same"'}),(0,i.jsx)(n.td,{children:"silhouettes must differ"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"the bot shoots out of nowhere"'}),(0,i.jsx)(n.td,{children:"damage requires prior LOS"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"there are 2 of them eliminating me"'}),(0,i.jsx)(n.td,{children:"1 killfeed per death"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:20-21"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"GOLDEN RULE: nothing gets committed with a RED invariant. And every new bug the owner\nreports becomes an invariant here \u2014 that's how it never comes back."})}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-an-invariant-is-here",children:"What an invariant is here"}),"\n",(0,i.jsxs)(n.p,{children:["An invariant is a ",(0,i.jsx)(n.strong,{children:"property of the game that can be measured without a human watching"}),",\nwith a ceiling or a range that has provenance. It is not a unit test: almost no invariant\ntests a function. They measure the ",(0,i.jsx)(n.strong,{children:"state of the game actually running"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Three forms, all present in the file:"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"1. Read from the source code."})," Cheap, runs in milliseconds, catches whole classes of bug.\nReal example, quoted verbatim from ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1439-1446"})," \u2014 the file's\ncomments are written in Portuguese:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// ARM1 \u2014 toda arma com luneta precisa de zoom de verdade. \"Snipers sem zoom\"\n// \xe9 reclama\xe7\xe3o literal; a solu\xe7\xe3o N\xc3O \xe9 tirar a luneta, \xe9 fazer a certa.\nconst bloco = gsrc.slice(0, gsrc.indexOf('};', gsrc.indexOf('const WEAPONS')) + 2);\nconst linhas = bloco.split('\\n').filter((l) => /^\\s*\\w+:\\s*\\{/.test(l));\nconst semZoom = linhas.filter((l) => /scope:\\s*true/.test(l) && !/spreadScope/.test(l))\n .map((l) => l.trim().split(':')[0]);\nput('ARM1', 'toda arma com scope:true declara spreadScope', semZoom.length === 0,\n semZoom.length ? semZoom.join(', ') : `${linhas.length} armas conferidas`);\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"2. Measured in the real game running in node."})," ",(0,i.jsx)(n.code,{children:"tools/eval/harness.mjs"})," boots the real\n",(0,i.jsx)(n.code,{children:"Game"})," class, with the real maps, with DOM/canvas stubbed. It is the ",(0,i.jsx)(n.strong,{children:"production code"}),"\nthat gets measured, not a reimplementation \u2014 ",(0,i.jsx)(n.code,{children:"tools/eval/botsim.mjs:8-9"}),": ",(0,i.jsx)(n.em,{children:'"if the number\nimproves here, it improved in the game"'}),". This is where BOT1\u2013BOT8, MAP1\u2013MAP3, CTF1, MAT1/MAT2,\nFOG1, TEX1, VM14, MOD1/MOD2 come from."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"3. Measured on the geometry of the assets."})," ",(0,i.jsx)(n.code,{children:"vm-mint-audit.mjs"})," opens every weapon GLB with\nits own GLB parser and projects the viewmodel onto the screen. This is where VM1\u2013VM19 come from."]}),"\n",(0,i.jsxs)(n.p,{children:["What does ",(0,i.jsx)(n.strong,{children:"not"})," belong here: an invariant that requires browser pixels. Those are marked\n",(0,i.jsx)(n.code,{children:"browser"})," and are skipped, with the reason stated \u2014 SwiftShader costs ~4 min per map load\non this machine (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:99"}),")."]}),"\n",(0,i.jsx)(n.h3,{id:"severity",children:"Severity"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"put(id, desc, ok, evid, sev)"})," accepts ",(0,i.jsx)(n.code,{children:"'crit'"})," (the default) or ",(0,i.jsx)(n.code,{children:"'warn'"}),"\n(",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:81-82"}),"). A red critical fails the PR. Warn is measured noise that\nsomeone needs to look at but doesn't block \u2014 it's where BOT1/BOT2/BOT3/BOT6/BOT7, ARM4 and\nARM5 live. ",(0,i.jsx)(n.code,{children:"skip()"})," is the third state, and it is ",(0,i.jsx)(n.strong,{children:"dangerous"}),": a green gate by absence of\ndata. That is why every ",(0,i.jsx)(n.code,{children:"skip"})," carries the reason."]}),"\n",(0,i.jsx)(n.h2,{id:"the-two-house-laws",children:"The two house laws"}),"\n",(0,i.jsx)(n.h3,{id:"law-1",children:"Law 1 \u2014 Intention that doesn't become an invariant is optimized away"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:452-461"}),"."]})," The case, verbatim:"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["the previous round took the gate from ",(0,i.jsx)(n.strong,{children:"16/21 to 19/21 without loosening a single ceiling"})," and\nwas still ",(0,i.jsx)(n.strong,{children:"REJECTED"})," by the owner, because to close VM5/VM10 it ",(0,i.jsxs)(n.strong,{children:["ZEROED the\n",(0,i.jsx)(n.code,{children:"VM_OFF"})," y"]}),' and silently changed the look. No invariant encoded "where the\nmuzzle sits", so the metric was optimized and the INTENT was destroyed. Goodhart\'s\nlaw, in full. ',(0,i.jsx)(n.strong,{children:"INTENTION THAT DOESN'T BECOME AN INVARIANT IS OPTIMIZED AWAY."})]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Read again what happened, because it is counterintuitive: the agent ",(0,i.jsx)(n.strong,{children:"did not cheat"}),". It\ndidn't loosen any ceiling. It genuinely raised the score. And the result was worse, because\n",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," is the term that ",(0,i.jsx)(n.strong,{children:"dominates the weapon's position on screen"})," \u2014 ",(0,i.jsx)(n.code,{children:"public/js/game.js:555"}),"\ndeclares ",(0,i.jsx)(n.code,{children:"VM_OFF = [0.03, -0.1000, 0]"}),", and ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1163"})," measures the\nsensitivity: ",(0,i.jsx)(n.em,{children:'"removing recuoZ moves the grip 3,5 cm; removing VM_OFF moves it 23 cm"'}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Zeroing that term closed two invariants and erased the aesthetic decision the owner had\nmade \u2014 which wasn't written anywhere the ruler could read."}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"The fix was not punishing the agent. It was writing the intention as an invariant."})," Today\nthere is VM12 (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:497"}),"): ",(0,i.jsx)(n.em,{children:'"CS 1.6 look: muzzle RIGHT below\nthe crosshair (y between 0,50 and 0,62) in the 2 aspects"'}),". With it in place, the same\noptimization goes ",(0,i.jsx)(n.strong,{children:"red"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"And the operational consequence, from the same comment:"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Whoever wants to change the look has to change ",(0,i.jsx)(n.strong,{children:"THIS ceiling"})," explicitly, in a diff the\nowner sees, instead of touching ",(0,i.jsx)(n.code,{children:"VM_OFF"}),' and reporting "+3 invariants".']}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"What this means for your PR",type:"tip",children:(0,i.jsxs)(n.p,{children:["If your change improves the gate's score, the first question is: ",(0,i.jsx)(n.strong,{children:"what did I change that\nthe gate doesn't look at?"}),' If the answer is "the look", "the feel" or "the vibe", write the\ninvariant before sending the PR \u2014 or explain in the PR why it doesn\'t fit.']})}),"\n",(0,i.jsx)(n.h3,{id:"law-2",children:"Law 2 \u2014 A ceiling without provenance is an opinion"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py:1-40"}),"."]})," That docstring is the house doctrine. The case:"]}),"\n",(0,i.jsxs)(n.p,{children:["For ",(0,i.jsx)(n.strong,{children:"three days"})," the weapons gate was solved against ",(0,i.jsx)(n.strong,{children:"asserted"})," numbers:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["VM12 required ",(0,i.jsx)(n.em,{children:'"muzzle at y \u2265 0,66"'}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsx)(n.code,{children:"vmattach.js"})," doc said ",(0,i.jsx)(n.em,{children:'"ENTIRE stock in the corner"'}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Neither of the two was measured on any image. According to ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:461-463"}),",\nthe 0,66 floor came from a comment in ",(0,i.jsx)(n.code,{children:"public/js/vmattach.js"})," \u2014 ",(0,i.jsx)(n.em,{children:'"the muzzle sits at ~0,66H"'})," \u2014\nwhich in turn came from a video someone watched. (The gate's comment points to\n",(0,i.jsx)(n.code,{children:"vmattach.js:387-392"}),"; today the text is at ",(0,i.jsx)(n.code,{children:"vmattach.js:395"}),", because the file moved. That is\nexactly why ",(0,i.jsx)(n.code,{children:"ARCH.md"})," is generated \u2014 see ",(0,i.jsx)(n.a,{href:"/docs/en/architecture",children:"Architecture"}),".)"]}),"\n",(0,i.jsxs)(n.p,{children:["The owner looked at the result and said, verbatim (",(0,i.jsx)(n.code,{children:"ref-measure.py:14-17"}),"):"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:'"it looks different from CS 1.6 and Quake and UT; in those 3 the weapon is always in the\nbottom-right corner and the stock is always OUTSIDE; after 3 days and an entire folder of\nreference neither you nor Kimi understood that."'}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Then the frames were ",(0,i.jsx)(n.strong,{children:"measured"}),". ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py"})," does color segmentation on the\nbottom-right quadrant, takes the largest connected component, and writes\n",(0,i.jsx)(n.code,{children:"tools/eval/ref_viewmodel.json"}),". Result:"]}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Frame"}),(0,i.jsx)(n.th,{children:"Muzzle (x, y)"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Screen area"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Axis angle"}),(0,i.jsx)(n.th,{children:"Crosses the right edge?"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"cs16_ak_dust.jpg"})}),(0,i.jsxs)(n.td,{children:["0,564 ; ",(0,i.jsx)(n.strong,{children:"0,513"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"9,76%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"28,0\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"cs16_m4_dust.jpg"})}),(0,i.jsxs)(n.td,{children:["0,569 ; ",(0,i.jsx)(n.strong,{children:"0,598"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"9,78%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"34,8\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"valorant_vandal.jpg"})}),(0,i.jsxs)(n.td,{children:["0,648 ; ",(0,i.jsx)(n.strong,{children:"0,587"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"13,09%"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"4,6\xb0"}),(0,i.jsx)(n.td,{children:"yes"})]})]})]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Both asserted numbers were wrong:"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The CS 1.6 muzzle sits at ",(0,i.jsx)(n.strong,{children:"0,513\u20130,598"})," \u2014 right below the crosshair (0,5), 1 to 10\npercentage points below center. Not at 0,66\u20130,93. The wrong floor was keeping our\nweapon ",(0,i.jsx)(n.strong,{children:"sunk"})," at 0,667\u20130,816 (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:472-475"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:["The stock ",(0,i.jsx)(n.strong,{children:"EXITS through the corner"})," in all 3 frames. Exiting is the standard, not the defect\n(",(0,i.jsx)(n.code,{children:"ref_viewmodel.json"})," \u2192 ",(0,i.jsx)(n.code,{children:"faixas.cruzaBordaDireita: true"})," in all 3)."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["And the collateral damage: with the false ceiling, the previous round's solver ",(0,i.jsx)(n.em,{children:'"proved"'})," that 3%\nof area was infeasible. The proof was correct ",(0,i.jsx)(n.strong,{children:"against that ceiling"})," \u2014 and it was the ceiling that\nwas false (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:476-478"}),")."]}),"\n",(0,i.jsxs)(n.p,{children:["The rule that remained, ",(0,i.jsx)(n.code,{children:"tools/eval/ref-measure.py:21-22"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"AN INVARIANT CEILING ONLY GETS IN WITH PROVENANCE \u2014 reference file, measured pixel, and\nthis script reproducing the number. A number without an image is an opinion."})}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Today the framing invariants carry the provenance in their own text: VM1 (range\n0,50\u20130,60, ref 0,520\u20130,565), VM3 (22\u201342\xb0, ref 28,0\xb0 and 34,8\xb0), VM5 (6\u201316%, ref\n9,76\u201313,09%), VM12 (0,50\u20130,62, ref 0,513\u20130,598), VM16 (slice at the right edge 0,02\u20130,20,\nref 0,053\u20130,095)."}),"\n",(0,i.jsx)(n.admonition,{title:"Provenance includes admitting what the image does NOT measure",type:"note",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:599-602"}),' refuses to create a ceiling for "how much of the weapon\nsits outside the frame", because what is outside is invisible in the photo \u2014 there is no way to\nknow whether the AK\'s stock ends 5 cm or 50 cm past the edge. The numbers remain in the JSON as\n',(0,i.jsx)(n.strong,{children:"evidence, without a gate"}),". That is provenance taken seriously: the ruler says where it stops\nknowing."]})}),"\n",(0,i.jsxs)(n.p,{children:["And the same rigor bites whoever wrote the ruler, in the most uncomfortable case possible: ",(0,i.jsx)(n.strong,{children:"the\ncharacter reference photos arrived, were measured, and were REJECTED by the ruler\nitself."})," ",(0,i.jsx)(n.code,{children:"tools/eval/char-probe.mjs:25-45"})," tells the whole episode \u2014 ",(0,i.jsx)(n.code,{children:"references/funkeiros/"}),"\nhas 23 files and ",(0,i.jsx)(n.code,{children:"references/palhacos/"})," has 21, all run through ",(0,i.jsx)(n.code,{children:"ref-body.py"}),", with the\nmasks ",(0,i.jsx)(n.strong,{children:"looked at"})," (",(0,i.jsx)(n.code,{children:"--masks"}),"). The verdict, said to our face by the comment itself: they are\nselfies and close-ups; the heuristic segmentation returns the hand, a piece of jacket or the hair\nof someone else in the background, and the shoulder/height ratio comes out between ",(0,i.jsx)(n.strong,{children:"0,42 and 3,78"})," when a human\nmeasures 0,259. About ~1 usable full-body photo remains \u2014 that is not a sample."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"ref-body.py"})," requires ",(0,i.jsx)(n.strong,{children:"6 accepted photos"})," for a ceiling to become measured, and it ",(0,i.jsx)(n.strong,{children:"says why\nit didn't"}),". So the absolute ceiling of CHR1 remains a ",(0,i.jsx)(n.strong,{children:"published fallback"})," (Drillis &\nContini 1966, via Winter), declared as such in the ",(0,i.jsx)(n.code,{children:"procedencia"})," field of the JSON and in the\nreport column."]}),"\n",(0,i.jsxs)(n.p,{children:["Notice what that means: having the photo is ",(0,i.jsx)(n.strong,{children:"not"})," having the measurement. It was easier to accept\nthat the data was bad than to promote a fragile measurement to a ceiling \u2014 and that is Law 2\napplied against the interest of whoever wrote the ruler."]}),"\n",(0,i.jsxs)(n.admonition,{type:"warning",children:[(0,i.jsxs)(n.mdxAdmonitionTitle,{children:[(0,i.jsx)(n.code,{children:"references/"})," does NOT come with the clone \u2014 and that is a decision, not carelessness"]}),(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"git ls-files references"})," returns ",(0,i.jsx)(n.strong,{children:"zero"}),". On 04/08/2026 the entire folder was\nuntracked by the owner's decision (\"references can stay local because we're going to build\nlocally\"): they are the UI target screens and the viewmodel reference frames, and they live only on\nhis machine."]}),(0,i.jsxs)(n.p,{children:["What ",(0,i.jsx)(n.strong,{children:"survives the clone are the NUMBERS measured from them"}),": ",(0,i.jsx)(n.code,{children:"tools/eval/ref_ui.json"})," and\n",(0,i.jsx)(n.code,{children:"tools/eval/ref_viewmodel.json"})," are versioned. That is the contract \u2014 if a ruler of yours\nneeds to run in CI, it reads the JSON, never the PNG. A ruler that opens an image from\n",(0,i.jsx)(n.code,{children:"references/"})," goes red on every machine that isn't the owner's, and red-by-environment is\nthe worst kind: it teaches whoever works here to ignore red."]})]}),"\n",(0,i.jsx)(n.h2,{id:"mutation-test",children:"Mutation test of the ruler itself"}),"\n",(0,i.jsx)(n.p,{children:"This is the part almost no project has, and it is where this repository is genuinely\ndifferent."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"A gate that doesn't move when you break the code on purpose is blind."})}),"\n",(0,i.jsxs)(n.p,{children:["The way to find that out is to mutate: take the fixed code, ",(0,i.jsx)(n.strong,{children:"undo the fix on\npurpose"}),", run the gate, and see whether it goes red. If it stays green, the gate is not\nmeasuring what you think it measures."]}),"\n",(0,i.jsx)(n.h3,{id:"the-case-20-22-green",children:"The case: 20/22 green with the fix removed"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsxs)(n.strong,{children:["Source: ",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:910-920"}),"."]})}),"\n",(0,i.jsxs)(n.p,{children:["The context: ",(0,i.jsx)(n.code,{children:"public/js/game.js:577"})," declares"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"const vmOffY = (aspect) => VM_OFF[1] * ((16 / 9) / (aspect || 16 / 9));\n"})}),"\n",(0,i.jsxs)(n.p,{children:["It is the per-aspect vertical framing fix \u2014 the reason the weapon sits in the same\nplace in 16:9 and in 3:2 (the owner plays in 3:2). It is ",(0,i.jsx)(n.strong,{children:"called"})," in the Y argument of\n",(0,i.jsx)(n.code,{children:"this.vm.root.position.set(...)"}),", at ",(0,i.jsx)(n.code,{children:"public/js/game.js:4873"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"The hole, measured in 08/2026:"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["the ",(0,i.jsx)(n.code,{children:"vmOff"})," step only checked ",(0,i.jsx)(n.code,{children:"/this\\.vm\\.root\\.position\\.set\\(\\s*VM_OFF\\[0\\]/"})," \u2014 the X\nterm. The Y term was checked by no one, and the auditor (",(0,i.jsx)(n.code,{children:"vm-mint-audit.mjs:196"}),",\n",(0,i.jsx)(n.code,{children:"loadOffYFn"}),") reads the ",(0,i.jsx)(n.strong,{children:"DECLARATION"})," ",(0,i.jsx)(n.code,{children:"const vmOffY = (aspect) => ..."})," by regex ",(0,i.jsx)(n.strong,{children:"without ever\nasking whether anyone CALLS it"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["Result: swapping in ",(0,i.jsx)(n.code,{children:"game.js"})," the call ",(0,i.jsx)(n.code,{children:"vmOffY(...)"})," for ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," in the Y argument\n\u2014 that is, ",(0,i.jsx)(n.strong,{children:"removing the per-aspect vertical framing fix entirely"})," \u2014 the\nwhole gate stayed ",(0,i.jsx)(n.strong,{children:"GREEN (20/22, with VM9, VM10, VM12 and VM15 all green)"}),". A\ngate that cannot tell the fixed build from the build without the fix is measuring\nnothing."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Notice the mechanism of the error, because it repeats in any language: ",(0,i.jsxs)(n.strong,{children:["the invariant\nwas reading the ",(0,i.jsx)(n.em,{children:"declaration"})," of a constant, and not the ",(0,i.jsx)(n.em,{children:"use"}),"."]})," Declaring and not calling is the\ncheapest way for a fix to vanish with the gate green."]}),"\n",(0,i.jsxs)(n.p,{children:["The repair was surgical and worth copying. AUD1 today separates the three arguments of\n",(0,i.jsx)(n.code,{children:"position.set(...)"})," with a ",(0,i.jsx)(n.strong,{children:"parenthesis scanner"})," \u2014 not ",(0,i.jsx)(n.code,{children:"split(',')"}),", which would cut\ninside the function call \u2014 and requires ",(0,i.jsx)(n.strong,{children:"by name"})," that the Y argument calls ",(0,i.jsx)(n.code,{children:"vmOffY("}),".\nAnd it closes the other path along with it (",(0,i.jsx)(n.code,{children:"tools/eval/invariants.mjs:1148-1151"}),"): the formula of\n",(0,i.jsx)(n.code,{children:"vmOffY"})," is ",(0,i.jsxs)(n.strong,{children:["read from ",(0,i.jsx)(n.code,{children:"game.js"})," and evaluated"]})," at 16/9, and it has to yield exactly ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["The two checks together cover the two ways for the fix to vanish: ",(0,i.jsx)(n.strong,{children:"deleting the CALL"}),"\n(mutation measured in 08/2026) or ",(0,i.jsx)(n.strong,{children:"tampering with the FORMULA"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"not-an-isolated-case",children:"It was not an isolated case \u2014 there were three"}),"\n",(0,i.jsx)(n.p,{children:"The same hole showed up in two other places, and each one became a new AUD1 step:"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Mutation"}),(0,i.jsx)(n.th,{children:"Score with the fix undone"}),(0,i.jsx)(n.th,{children:"Cause of the false green"}),(0,i.jsx)(n.th,{children:"Where"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Swap ",(0,i.jsx)(n.code,{children:"vmOffY(...)"})," for ",(0,i.jsx)(n.code,{children:"VM_OFF[1]"})," in the Y argument"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"20/22 green"})}),(0,i.jsx)(n.td,{children:"the invariant read the declaration, not the use"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:910-920"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Swap ",(0,i.jsx)(n.code,{children:"g.rotation.set(pit, yaw, t.roll)"})," for ",(0,i.jsx)(n.code,{children:"g.rotation.set(0, 0, t.roll)"})]}),(0,i.jsx)(n.td,{children:"green"}),(0,i.jsxs)(n.td,{children:["the ",(0,i.jsx)(n.code,{children:"VM_FRAME.cls"})," table still holds the angles, and the three mirrors still match ",(0,i.jsx)(n.strong,{children:"each other"})]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:932-944"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Delete ",(0,i.jsx)(n.code,{children:"* (weaponCFG(id).vm ?? 1)"})," from the mesh scale"]}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.strong,{children:"28/37 green, AUD1 included"}),' ("worst \u0394scale 0.0004")']}),(0,i.jsxs)(n.td,{children:["both ends read ",(0,i.jsx)(n.code,{children:"vm"})," from ",(0,i.jsx)(n.code,{children:"weapons.js"}),"; ",(0,i.jsxs)(n.strong,{children:[(0,i.jsx)(n.code,{children:"game.js"})," is never asked"]})," \u2014 it was the auditor checking itself"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:971-975"})})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Mutate ",(0,i.jsx)(n.code,{children:"this._adsPose['pistol']"})]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"20/22 green"})}),(0,i.jsx)(n.td,{children:"ADS had no invariant at all"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"invariants.mjs:1185"})})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"The common pattern of the four is the same, and it is what you should look for in your invariant:"}),"\n",(0,i.jsx)(n.admonition,{title:"The false-green pattern",type:"danger",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"The ruler is checking a copy of the rule instead of the game."})," Whether because it reads the declaration\nand not the use, or because it compares two mirrors that read the same source, or because the\nparameter table stays correct while no one applies it. If the two ends of your\ncomparison can stay consistent ",(0,i.jsx)(n.strong,{children:"without going through the production code"}),", your\ninvariant is blind."]})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"tools/eval/mat-check.mjs:18-27"})," solves this in the most direct way possible: the body of\n",(0,i.jsx)(n.code,{children:"fixVmMaterials"})," is ",(0,i.jsxs)(n.strong,{children:["cut out of ",(0,i.jsx)(n.code,{children:"game.js"})," and executed"]})," against a probe material. If the\ncode changes, the ruler changes with it. ",(0,i.jsx)(n.em,{children:'"a ruler that carries a COPY of the rule lies on the day\nthe rule changes."'})]}),"\n",(0,i.jsxs)(n.h3,{id:"mutation-first-class",children:["Mutation as a first-class thing: ",(0,i.jsx)(n.code,{children:"ui-check.mjs"})]}),"\n",(0,i.jsxs)(n.p,{children:["The UI harness has a ",(0,i.jsx)(n.strong,{children:"versioned mutation table"}),", and each one declares which gate\nhas to go red. ",(0,i.jsx)(n.code,{children:"tools/eval/ui-check.mjs:1046-1050"}),":"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["Each mutation UNDOES one of this round's fixes (or punches through a gate on purpose) and says\nwhich gate MUST go red. ",(0,i.jsx)(n.strong,{children:"A ruler that does not fail the previous version of\nits own file is not a ruler, it is decoration."})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Running one:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"MUT=ui1_ctf_scrim_fraco node tools/eval/ui-check.mjs # expects UI1 to go RED\nMUT=ui3_prompt_na_mira node tools/eval/ui-check.mjs # expects UI3 to go RED\nMUT=ui2_prompt_eterno node tools/eval/ui-check.mjs # expects UI2 to go RED\nMUT=ui4_ctf_sem_relogio node tools/eval/ui-check.mjs # expects UI4 to go RED\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The 7 mutations are in ",(0,i.jsx)(n.code,{children:"tools/eval/ui-check.mjs:1051-1134"}),". Two mechanics: ",(0,i.jsx)(n.code,{children:"css"})," rewrites\n",(0,i.jsx)(n.code,{children:"public/style.css"})," ",(0,i.jsx)(n.strong,{children:"read in memory"})," (never on disk \u2014 other agents are editing the\nfile right now), and ",(0,i.jsx)(n.code,{children:"sim"})," monkey-patches the already-booted ",(0,i.jsx)(n.code,{children:"Game"})," object. If the ",(0,i.jsx)(n.code,{children:"css"})," mutation\nmatches nothing, the script exits with code 2 saying ",(0,i.jsx)(n.em,{children:'"the CSS changed shape"'})," \u2014 because a\nmutation that doesn't apply is also a false green (",(0,i.jsx)(n.code,{children:"ui-check.mjs:1164"}),")."]}),"\n",(0,i.jsx)(n.h2,{id:"how-to-write-an-invariant",children:"How to write an invariant"}),"\n",(0,i.jsx)(n.p,{children:"Checklist, in order:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Start from the defect's sentence."})," Verbatim, with the words of whoever complained. Every\nharness in this codebase starts that way, and it is not style: it is what keeps the invariant from\nmeasuring something else. See the header of ",(0,i.jsx)(n.code,{children:"tools/eval/map-check.mjs:5-12"})," \u2014 five sentences from the owner,\nfive invariants."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Translate it into a measurable quantity."}),' "the players are SUBMERGED UNDER THE\nSTATUE" \u2192 ',(0,i.jsx)(n.em,{children:"there is visible map geometry whose top rises more than 0,30 m above the floor\nlocal to that point"})," (MAP1). Note that the operational definition includes ",(0,i.jsx)(n.strong,{children:"why 0,30 m"}),':\nit is the step the body climbs; above that it is not "stepping over", it is "being inside".']}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Find the ceiling's provenance."})," Reference file + measured pixel + script that\nreproduces it. If it doesn't exist, ",(0,i.jsx)(n.strong,{children:"say it is a fallback"})," and cite the published source, as C1\ndoes. Never invent the number."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Measure the production code, not a copy of it."})," Import the real module, cut the\nfunction out of the file and execute it, or require the call by name in the source text."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Mutate and confirm it goes red."})," Undo the fix you just made and\nrun the gate. If it stays green, your invariant is blind \u2014 go back to step 4. If it can\nbe automated, register the mutation in a table, as ",(0,i.jsx)(n.code,{children:"ui-check.mjs"})," does."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Write the evidence, not just the boolean."})," The fourth argument of ",(0,i.jsx)(n.code,{children:"put()"})," is what\nsomeone will read three months from now: ",(0,i.jsx)(n.code,{children:'"0,504 a 0,619 da altura em 52 medidas | 0 fora da faixa"'})," is useful; ",(0,i.jsx)(n.code,{children:'"ok"'})," is not."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Write the provenance comment above it."})," In Portuguese, saying what\nhappened when the number was wrong. That comment is what keeps the next round\nfrom redoing the mistake."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"anti-patterns",children:"Anti-patterns that have already cost dearly here"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Anti-pattern"}),(0,i.jsx)(n.th,{children:"What it produced"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Reading a constant's declaration instead of its use"}),(0,i.jsx)(n.td,{children:"20/22 green with the fix removed"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Two mirrors that read the same source"}),(0,i.jsx)(n.td,{children:'28/37 green, "worst \u0394scale 0.0004", with the knob turned off'})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Format adapter silently broken"}),(0,i.jsxs)(n.td,{children:["VM1\u2013VM6 have been ",(0,i.jsx)(n.strong,{children:"SKIPPED since the auditor exists"})," \u2014 6 viewmodel invariants that never ran once (",(0,i.jsx)(n.code,{children:"invariants.mjs:121-127"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Measuring the gap against the wrong local floor"}),(0,i.jsxs)(n.td,{children:["a pickup inside the pool reported gap ",(0,i.jsx)(n.strong,{children:"0,0000 \u2014 GREEN"})," (",(0,i.jsx)(n.code,{children:"pickup-check.mjs:20-23"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:'"waypoint \u2264 3 m" as a reachability proxy'}),(0,i.jsxs)(n.td,{children:["74 false positives and green in a closed pocket (",(0,i.jsx)(n.code,{children:"pickup-check.mjs:34-42"}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Floor without ceiling"}),(0,i.jsxs)(n.td,{children:['"muzzle \u2265 0,66" accepts the muzzle at 0,95 (weapon in the basement) \u2014 that is how we got to 0,816 (',(0,i.jsx)(n.code,{children:"invariants.mjs:432-434"}),")"]})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"doctrine-vs-skill",children:"This page is the doctrine. The step-by-step is a skill"}),"\n",(0,i.jsxs)(n.p,{children:["What to do, in order, when someone reports a defect \u2014 reproduce, measure before\nfixing, refute the obvious guess, mutate the ruler, run the gate in the right order and report\nwhat was ",(0,i.jsx)(n.strong,{children:"not"})," verified \u2014 is in ",(0,i.jsx)(n.code,{children:".claude/skills/bug-hunt/SKILL.md"}),", with the real case\nthat paid for each rule. It is written for agents ",(0,i.jsx)(n.strong,{children:"and"})," for people, and it points back to\nthis page instead of repeating it."]}),"\n",(0,i.jsx)(n.h2,{id:"running-the-gate",children:"Running the gate"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"node tools/eval/invariants.mjs # everything that runs without a browser\nnode tools/eval/invariants.mjs --json # machine-readable output\nnpm run check # syntax + gate + vm + recoil + bots\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Current production, data, and debt sources: ",(0,i.jsx)(n.a,{href:"./status",children:"Current state"}),"."]})]})}function l(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>o});var s=t(6540);const i={},r=s.createContext(i);function a(e){const n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/public/docs/en/assets/js/ed1a65a0.28d4867f.js b/public/docs/en/assets/js/ed1a65a0.813e3890.js
similarity index 99%
rename from public/docs/en/assets/js/ed1a65a0.28d4867f.js
rename to public/docs/en/assets/js/ed1a65a0.813e3890.js
index a490a03b0..67fcd46f8 100644
--- a/public/docs/en/assets/js/ed1a65a0.28d4867f.js
+++ b/public/docs/en/assets/js/ed1a65a0.813e3890.js
@@ -1 +1 @@
-"use strict";(globalThis.webpackChunkcoro_solto_docs||=[]).push([[502],{8909(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>l,frontMatter:()=>d,metadata:()=>t,toc:()=>h});const t=JSON.parse('{"id":"stack","title":"Stack and tools","description":"Three.js/WebGL with no build, Astro on Vercel, Supabase, the asset generation pipeline (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform and the agent skills \u2014 each with the version read from package.json.","source":"@site/i18n/en/docusaurus-plugin-content-docs/current/stack.md","sourceDirName":".","slug":"/stack","permalink":"/docs/en/stack","draft":false,"unlisted":false,"editUrl":"https://github.com/corosolto/client/tree/main/docs/docs/stack.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"id":"stack","title":"Stack and tools","sidebar_label":"Stack and tools","sidebar_position":2,"slug":"/stack","description":"Three.js/WebGL with no build, Astro on Vercel, Supabase, the asset generation pipeline (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform and the agent skills \u2014 each with the version read from package.json."},"sidebar":"dev","previous":{"title":"Getting started","permalink":"/docs/en/"},"next":{"title":"AI instrumentation","permalink":"/docs/en/ai-instrumentation"}}');var r=s(4848),i=s(8453);const d={id:"stack",title:"Stack and tools",sidebar_label:"Stack and tools",sidebar_position:2,slug:"/stack",description:"Three.js/WebGL with no build, Astro on Vercel, Supabase, the asset generation pipeline (mint.gg, Tripo3D, Meshy, OpenRouter), Playwright, gltf-transform and the agent skills \u2014 each with the version read from package.json."},o="Stack and tools",c={},h=[{value:"The two zones, and why the boundary is hard",id:"the-two-zones",level:2},{value:"public/ \u2014 the GAME: Three.js, WebGL, zero build",id:"public-the-game",level:3},{value:"src/ \u2014 the SITE: Astro with SSR on Vercel",id:"src-the-site",level:3},{value:"Database \u2014 managed Postgres, RLS and telemetry",id:"database",level:3},{value:"Asset generation \u2014 what is AI-generated, and by which service",id:"asset-generation",level:2},{value:"Characters: mint.gg",id:"characters-mintgg",level:3},{value:"3D props: Tripo3D and Meshy",id:"props-3d",level:3},{value:"2D art: OpenRouter",id:"art-2d-openrouter",level:3},{value:"The keys",id:"the-keys",level:3},{value:"GLB optimization: gltf-transform and meshoptimizer",id:"glb-optimization",level:2},{value:"Playwright \u2014 every harness that needs a browser",id:"playwright",level:2},{value:"Agent skills",id:"agent-skills",level:2},{value:"The gauntlet loop",id:"the-gauntlet-loop",level:3},{value:"The documentation",id:"the-documentation",level:2}];function a(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:["\n",(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"stack-and-tools",children:"Stack and tools"})}),"\n",(0,r.jsxs)(n.p,{children:["This page answers the question ",(0,r.jsx)(n.em,{children:'"what is this made with?"'})," \u2014 and it answers with the ",(0,r.jsx)(n.strong,{children:"declared\nversion"}),", not the remembered one. The table below is generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),"\nfrom ",(0,r.jsx)(n.code,{children:"package.json"}),", ",(0,r.jsx)(n.code,{children:"docs/package.json"})," and the vendored Three.js itself."]}),"\n","\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Layer"}),(0,r.jsx)(n.th,{children:"Tool"}),(0,r.jsx)(n.th,{children:"Version"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"3D engine (WebGL)"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Three.js"}),", vendored"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"r160"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Game"}),(0,r.jsxs)(n.td,{children:["vanilla ES modules, ",(0,r.jsx)(n.strong,{children:"zero build"})]}),(0,r.jsx)(n.td,{children:"44 files"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Site"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Astro"})," with SSR"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^7.1.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Hosting"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Vercel"})," adapter"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^11.0.3"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Database"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"managed Postgres"})," (RLS; private schema)"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^2.110.7"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Browser checks"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Playwright"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^1.62.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"GLB pipeline"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"gltf-transform"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^4.4.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Mesh compression"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"meshoptimizer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^1.2.0"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Images"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"sharp"})," \xb7 ",(0,r.jsx)(n.strong,{children:"resvg"})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"^0.35.3"})," \xb7 ",(0,r.jsx)(n.code,{children:"^2.6.2"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"This documentation"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Docusaurus"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"3.6.3"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"CI runtime"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Node"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"22"})})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Three.js comes from ",(0,r.jsx)(n.code,{children:"public/vendor/three.module.js"}),". Of the scripts in ",(0,r.jsx)(n.code,{children:"tools/"}),", ",(0,r.jsx)(n.strong,{children:"109"})," import Playwright, ",(0,r.jsx)(n.strong,{children:"37"})," import gltf-transform, and ",(0,r.jsx)(n.strong,{children:"4"})," import meshoptimizer."]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["Block generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,r.jsx)(n.code,{children:"dependencies/devDependencies do package.json \xb7 REVISION de public/vendor/three.module.js"})]}),"\n"]}),"\n","\n",(0,r.jsx)(n.h2,{id:"the-two-zones",children:"The two zones, and why the boundary is hard"}),"\n",(0,r.jsxs)(n.p,{children:["The repository has ",(0,r.jsx)(n.strong,{children:"two applications with opposite rules"}),", and almost every misunderstanding from\nnewcomers is born from treating them as one."]}),"\n",(0,r.jsxs)(n.h3,{id:"public-the-game",children:[(0,r.jsx)(n.code,{children:"public/"})," \u2014 the GAME: Three.js, WebGL, zero build"]}),"\n",(0,r.jsxs)(n.p,{children:["The game is ",(0,r.jsx)(n.strong,{children:"vanilla JavaScript with ES modules served raw"}),". There is no bundler, no\ntranspiler, no build step. The browser downloads ",(0,r.jsx)(n.code,{children:"public/js/game.js"})," exactly as it is in the\nrepository."]}),"\n",(0,r.jsxs)(n.p,{children:["This is a ",(0,r.jsx)(n.strong,{children:"design decision, not laziness"}),", and it pays off in three places:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"The game runs by dragging the folder onto any static host."})," It does not depend on Astro,\ndoes not depend on Vercel, does not depend on npm at runtime. It is what makes it viable to ship on a\nportal (CrazyGames, itch) without rewriting anything."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["The harness can boot the ",(0,r.jsx)(n.code,{children:"Game"})," class in pure node."]})," ",(0,r.jsx)(n.code,{children:"tools/eval/harness.mjs"}),"\nimports the ",(0,r.jsx)(n.strong,{children:"production code"})," with DOM and canvas stubbed, and measures the real game in\nseconds. A bundler in the middle would break that \u2014 and without that there is no gate."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"node --check"})," on each file is a complete syntax test"]})," (",(0,r.jsx)(n.code,{children:"npm run syntax"}),"),\nbecause the file node parses is byte for byte what the browser executes."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The price, which is also real: ",(0,r.jsx)(n.strong,{children:"cache"}),". Without a build there is no hash in the filename, so\ninvalidation is manual \u2014 the import map's ",(0,r.jsx)(n.code,{children:"?v="}),". The rule and what it has already cost are in\n",(0,r.jsx)(n.a,{href:"/docs/en/#the-two-zones",children:"Getting started"}),", in one place only."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Three.js is vendored"})," at ",(0,r.jsx)(n.code,{children:"public/vendor/three.module.js"})," (plus ",(0,r.jsx)(n.code,{children:"vendor/addons/"}),").\nNo CDN and no runtime dependency: the import map points to the local file. Do not\nadd a CDN or a runtime package without opening an issue."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"WebGL is the target, and a weak machine is a requirement."})," There is a ",(0,r.jsx)(n.code,{children:"quality: 'low'"})," path with no\npost-processing and a querystring kill-switch for every risky change (",(0,r.jsx)(n.code,{children:"?bloom=0"}),",\n",(0,r.jsx)(n.code,{children:"?ao=0"}),", ",(0,r.jsx)(n.code,{children:"?fxaa=0"}),", ",(0,r.jsx)(n.code,{children:"?water=0"}),"). Every graphics change that requires extra rendering has to\ndeclare its measured cost."]}),"\n",(0,r.jsxs)(n.h3,{id:"src-the-site",children:[(0,r.jsx)(n.code,{children:"src/"})," \u2014 the SITE: Astro with SSR on Vercel"]}),"\n",(0,r.jsxs)(n.p,{children:["The site is ",(0,r.jsx)(n.a,{href:"https://astro.build",children:"Astro"})," with the Vercel adapter. ",(0,r.jsx)(n.code,{children:"astro.config.mjs"})," is set to\n",(0,r.jsx)(n.code,{children:"output: 'static'"})," ",(0,r.jsx)(n.strong,{children:"with adapter"}),", and the routes that need a server opt in with\n",(0,r.jsx)(n.code,{children:"export const prerender = false"})," one by one \u2014 the case of ",(0,r.jsx)(n.code,{children:"/ranking"}),", ",(0,r.jsx)(n.code,{children:"/u/*"}),",\n",(0,r.jsx)(n.code,{children:"/sitemap.xml"})," and all the ",(0,r.jsx)(n.code,{children:"/api/*"})," routes."]}),"\n",(0,r.jsx)(n.p,{children:"Here a framework is welcome. The rules that apply:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["the Supabase ",(0,r.jsx)(n.code,{children:"service_role"})," lives ",(0,r.jsx)(n.strong,{children:"server-side only"})," and never reaches the browser;"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"site"})," in ",(0,r.jsx)(n.code,{children:"astro.config.mjs"})," is ",(0,r.jsxs)(n.strong,{children:["with ",(0,r.jsx)(n.code,{children:"www"})]}),", and every canonical comes from there;"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"vercel.json"})," carries the security headers (CSP, HSTS, nosniff, Referrer-Policy,\nPermissions-Policy) and the CDN cache."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["And the gotcha that costs everyone their first hour: ",(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"src/pages/index.astro"})," IS the\ngame"]}),", served at the ",(0,r.jsx)(n.code,{children:"/"})," route. There is no ",(0,r.jsx)(n.code,{children:"public/index.html"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"database",children:"Database \u2014 managed Postgres, RLS and telemetry"}),"\n",(0,r.jsxs)(n.p,{children:["The ranking and the telemetry live in a managed Postgres. Schema and migrations are\nprivate (outside the repo \u2014 a security decision); the runtime only uses the envs.\noptional obfuscation that was delivered ready and ",(0,r.jsx)(n.strong,{children:"deliberately not applied"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Security does not come from hiding the ",(0,r.jsx)(n.code,{children:"anon"})," key \u2014 it is public by design, although the current\nclient does not receive it. It comes from the ",(0,r.jsx)(n.em,{children:"policies"}),", from the per-column grants and from the rate limit counted\nin Postgres (",(0,r.jsx)(n.code,{children:"src/lib/ratelimit.ts"})," + RPC ",(0,r.jsx)(n.code,{children:"rl_take"}),"), not in lambda memory."]}),"\n",(0,r.jsxs)(n.p,{children:["Player identity uses a stable UID to select the account and a token to authenticate\nthe session; the nickname is display data. Old clients and a database pending the\nprivate migration retain a temporary ",(0,r.jsx)(n.code,{children:"nickname + token"})," fallback."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"The ranking is off today"})," (",(0,r.jsx)(n.code,{children:"RANKING_ON"})," in ",(0,r.jsx)(n.code,{children:"src/lib/site.ts"}),") and was replaced by\nanonymous telemetry. It is a flag, not a removal \u2014 details in ",(0,r.jsx)(n.a,{href:"./status",children:"Current state"}),"."]}),"\n",(0,r.jsx)(n.admonition,{title:"None of this is required to run the game",type:"note",children:(0,r.jsxs)(n.p,{children:["Without the Supabase variables the site boots the same: the ranking routes respond\n",(0,r.jsx)(n.code,{children:"503 not_configured"})," and the pages show the notice. The game in ",(0,r.jsx)(n.code,{children:"public/"})," ",(0,r.jsx)(n.strong,{children:"uses none of\nthem"}),". See ",(0,r.jsx)(n.code,{children:".env.example"}),"."]})}),"\n",(0,r.jsx)(n.h2,{id:"asset-generation",children:"Asset generation \u2014 what is AI-generated, and by which service"}),"\n",(0,r.jsxs)(n.p,{children:["Almost every 3D and 2D asset in this game is ",(0,r.jsx)(n.strong,{children:"generated"}),", not hand-modeled. The real flow, not the\nhypothetical one:"]}),"\n","\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Service"}),(0,r.jsx)(n.th,{children:"What it generates"}),(0,r.jsx)(n.th,{children:"Script"}),(0,r.jsx)(n.th,{children:"Key"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"mint.gg"})," (Mint MCP)"]}),(0,r.jsx)(n.td,{children:"rigged characters, packs, animation"}),(0,r.jsxs)(n.td,{children:["MCP tools; ",(0,r.jsx)(n.code,{children:"mint-assets.json"})," records the result"]}),(0,r.jsx)(n.td,{children:"owner account via MCP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Tripo3D"})}),(0,r.jsx)(n.td,{children:"3D props from text"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs --provider tripo"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"TRIPO_API_KEY"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Meshy"})}),(0,r.jsx)(n.td,{children:"3D props and rigging"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs --provider meshy"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"MESHY_API_KEY"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"OpenRouter"})}),(0,r.jsx)(n.td,{children:"2D art"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-image.mjs"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"OPENROUTER_API_KEY"})})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"mint-assets.json"})," records ",(0,r.jsx)(n.strong,{children:"7 assets"})," generated through Mint (3 ",(0,r.jsx)(n.code,{children:"mint-model"})," \xb7 4 ",(0,r.jsx)(n.code,{children:"mint-asset-pack"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["API keys live in the gitignored root ",(0,r.jsx)(n.code,{children:".env"}),"; generation is offline and the game runs without them."]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["Block generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,r.jsx)(n.code,{children:"git grep -l SDK -- tools/ | grep .mjs \xb7 mint-assets.json"})]}),"\n"]}),"\n","\n",(0,r.jsx)(n.h3,{id:"characters-mintgg",children:"Characters: mint.gg"}),"\n",(0,r.jsxs)(n.p,{children:["The playable characters are rigged GLBs generated by ",(0,r.jsx)(n.strong,{children:"Mint"})," (mint.gg), through the MCP\ntools \u2014 ",(0,r.jsx)(n.code,{children:"start_model_generation"})," with ",(0,r.jsx)(n.code,{children:"riggable_character"})," in T-pose and empty hands, then\n",(0,r.jsx)(n.code,{children:"animate_generated_model"})," to come out with a skeleton."]}),"\n",(0,r.jsx)(n.p,{children:"Two non-obvious facts that save money and rounds:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Mint's base model does not come rigged."})," The skeleton only appears at the animation step.\nThe cheap path for a new character is: generate the base \u2192 rig it with ",(0,r.jsx)(n.strong,{children:"one"})," clip \u2192\nuse its ",(0,r.jsx)(n.code,{children:"rigged_character_glb"})," \u2192 reuse the shared clips."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Meshy rigs share the same bone names"})," (",(0,r.jsx)(n.code,{children:"Hips"}),", ",(0,r.jsx)(n.code,{children:"Spine"}),", ",(0,r.jsx)(n.code,{children:"Head"}),",\n",(0,r.jsx)(n.code,{children:"RightHand"}),"\u2026), so a clip pack generated once matches by name on any rig in the\nfamily. That is why ",(0,r.jsx)(n.code,{children:"public/models/anims/"})," has shared clips and per-character clips\nat the same time, and why the ",(0,r.jsx)(n.code,{children:"index.json"})," manifest exists (",(0,r.jsx)(n.code,{children:"npm run anims"}),") \u2014 without it the\ngame requested clips from characters that do not have them and filled the console with 404s."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"mint-assets.json"})," is the record of what was generated: ",(0,r.jsx)(n.code,{children:"assetId"}),", ",(0,r.jsx)(n.code,{children:"chatUrl"})," and a note on what\nwent wrong in the previous attempt. ",(0,r.jsx)(n.strong,{children:"Without that record there is no reviewing and no regenerating"})," \u2014\nthe asset becomes a binary with no provenance sitting in the repository."]}),"\n",(0,r.jsx)(n.h3,{id:"props-3d",children:"3D props: Tripo3D and Meshy"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs"})," generates a prop from text, downloads the GLB and writes it ",(0,r.jsx)(n.strong,{children:"already optimized"})," into\n",(0,r.jsx)(n.code,{children:"public/models/props/"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'node tools/gen-asset.mjs --prompt "caixa de som de baile" --id caixa_som\nnode tools/gen-asset.mjs --provider meshy --prompt "carro tunado" --id carro_tunado\nnode tools/gen-asset.mjs --resume public/ \u2014 the GAME: Three.js, WebGL, zero build",id:"public-the-game",level:3},{value:"src/ \u2014 the SITE: Astro with SSR on Vercel",id:"src-the-site",level:3},{value:"Database \u2014 managed Postgres, RLS and telemetry",id:"database",level:3},{value:"Asset generation \u2014 what is AI-generated, and by which service",id:"asset-generation",level:2},{value:"Characters: mint.gg",id:"characters-mintgg",level:3},{value:"3D props: Tripo3D and Meshy",id:"props-3d",level:3},{value:"2D art: OpenRouter",id:"art-2d-openrouter",level:3},{value:"The keys",id:"the-keys",level:3},{value:"GLB optimization: gltf-transform and meshoptimizer",id:"glb-optimization",level:2},{value:"Playwright \u2014 every harness that needs a browser",id:"playwright",level:2},{value:"Agent skills",id:"agent-skills",level:2},{value:"The gauntlet loop",id:"the-gauntlet-loop",level:3},{value:"The documentation",id:"the-documentation",level:2}];function a(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:["\n",(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"stack-and-tools",children:"Stack and tools"})}),"\n",(0,r.jsxs)(n.p,{children:["This page answers the question ",(0,r.jsx)(n.em,{children:'"what is this made with?"'})," \u2014 and it answers with the ",(0,r.jsx)(n.strong,{children:"declared\nversion"}),", not the remembered one. The table below is generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),"\nfrom ",(0,r.jsx)(n.code,{children:"package.json"}),", ",(0,r.jsx)(n.code,{children:"docs/package.json"})," and the vendored Three.js itself."]}),"\n","\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Layer"}),(0,r.jsx)(n.th,{children:"Tool"}),(0,r.jsx)(n.th,{children:"Version"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"3D engine (WebGL)"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Three.js"}),", vendored"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"r160"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Game"}),(0,r.jsxs)(n.td,{children:["vanilla ES modules, ",(0,r.jsx)(n.strong,{children:"zero build"})]}),(0,r.jsx)(n.td,{children:"44 files"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Site"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Astro"})," with SSR"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^7.1.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Hosting"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Vercel"})," adapter"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^11.0.6"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Database"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"managed Postgres"})," (RLS; private schema)"]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^2.110.7"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Browser checks"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Playwright"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^1.62.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"GLB pipeline"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"gltf-transform"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^4.4.1"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Mesh compression"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"meshoptimizer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"^1.2.0"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Images"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"sharp"})," \xb7 ",(0,r.jsx)(n.strong,{children:"resvg"})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"^0.35.3"})," \xb7 ",(0,r.jsx)(n.code,{children:"^2.6.2"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"This documentation"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Docusaurus"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"3.6.3"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"CI runtime"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Node"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"22"})})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Three.js comes from ",(0,r.jsx)(n.code,{children:"public/vendor/three.module.js"}),". Of the scripts in ",(0,r.jsx)(n.code,{children:"tools/"}),", ",(0,r.jsx)(n.strong,{children:"110"})," import Playwright, ",(0,r.jsx)(n.strong,{children:"37"})," import gltf-transform, and ",(0,r.jsx)(n.strong,{children:"4"})," import meshoptimizer."]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["Block generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,r.jsx)(n.code,{children:"dependencies/devDependencies do package.json \xb7 REVISION de public/vendor/three.module.js"})]}),"\n"]}),"\n","\n",(0,r.jsx)(n.h2,{id:"the-two-zones",children:"The two zones, and why the boundary is hard"}),"\n",(0,r.jsxs)(n.p,{children:["The repository has ",(0,r.jsx)(n.strong,{children:"two applications with opposite rules"}),", and almost every misunderstanding from\nnewcomers is born from treating them as one."]}),"\n",(0,r.jsxs)(n.h3,{id:"public-the-game",children:[(0,r.jsx)(n.code,{children:"public/"})," \u2014 the GAME: Three.js, WebGL, zero build"]}),"\n",(0,r.jsxs)(n.p,{children:["The game is ",(0,r.jsx)(n.strong,{children:"vanilla JavaScript with ES modules served raw"}),". There is no bundler, no\ntranspiler, no build step. The browser downloads ",(0,r.jsx)(n.code,{children:"public/js/game.js"})," exactly as it is in the\nrepository."]}),"\n",(0,r.jsxs)(n.p,{children:["This is a ",(0,r.jsx)(n.strong,{children:"design decision, not laziness"}),", and it pays off in three places:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"The game runs by dragging the folder onto any static host."})," It does not depend on Astro,\ndoes not depend on Vercel, does not depend on npm at runtime. It is what makes it viable to ship on a\nportal (CrazyGames, itch) without rewriting anything."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["The harness can boot the ",(0,r.jsx)(n.code,{children:"Game"})," class in pure node."]})," ",(0,r.jsx)(n.code,{children:"tools/eval/harness.mjs"}),"\nimports the ",(0,r.jsx)(n.strong,{children:"production code"})," with DOM and canvas stubbed, and measures the real game in\nseconds. A bundler in the middle would break that \u2014 and without that there is no gate."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"node --check"})," on each file is a complete syntax test"]})," (",(0,r.jsx)(n.code,{children:"npm run syntax"}),"),\nbecause the file node parses is byte for byte what the browser executes."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The price, which is also real: ",(0,r.jsx)(n.strong,{children:"cache"}),". Without a build there is no hash in the filename, so\ninvalidation is manual \u2014 the import map's ",(0,r.jsx)(n.code,{children:"?v="}),". The rule and what it has already cost are in\n",(0,r.jsx)(n.a,{href:"/docs/en/#the-two-zones",children:"Getting started"}),", in one place only."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Three.js is vendored"})," at ",(0,r.jsx)(n.code,{children:"public/vendor/three.module.js"})," (plus ",(0,r.jsx)(n.code,{children:"vendor/addons/"}),").\nNo CDN and no runtime dependency: the import map points to the local file. Do not\nadd a CDN or a runtime package without opening an issue."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"WebGL is the target, and a weak machine is a requirement."})," There is a ",(0,r.jsx)(n.code,{children:"quality: 'low'"})," path with no\npost-processing and a querystring kill-switch for every risky change (",(0,r.jsx)(n.code,{children:"?bloom=0"}),",\n",(0,r.jsx)(n.code,{children:"?ao=0"}),", ",(0,r.jsx)(n.code,{children:"?fxaa=0"}),", ",(0,r.jsx)(n.code,{children:"?water=0"}),"). Every graphics change that requires extra rendering has to\ndeclare its measured cost."]}),"\n",(0,r.jsxs)(n.h3,{id:"src-the-site",children:[(0,r.jsx)(n.code,{children:"src/"})," \u2014 the SITE: Astro with SSR on Vercel"]}),"\n",(0,r.jsxs)(n.p,{children:["The site is ",(0,r.jsx)(n.a,{href:"https://astro.build",children:"Astro"})," with the Vercel adapter. ",(0,r.jsx)(n.code,{children:"astro.config.mjs"})," is set to\n",(0,r.jsx)(n.code,{children:"output: 'static'"})," ",(0,r.jsx)(n.strong,{children:"with adapter"}),", and the routes that need a server opt in with\n",(0,r.jsx)(n.code,{children:"export const prerender = false"})," one by one \u2014 the case of ",(0,r.jsx)(n.code,{children:"/ranking"}),", ",(0,r.jsx)(n.code,{children:"/u/*"}),",\n",(0,r.jsx)(n.code,{children:"/sitemap.xml"})," and all the ",(0,r.jsx)(n.code,{children:"/api/*"})," routes."]}),"\n",(0,r.jsx)(n.p,{children:"Here a framework is welcome. The rules that apply:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["the Supabase ",(0,r.jsx)(n.code,{children:"service_role"})," lives ",(0,r.jsx)(n.strong,{children:"server-side only"})," and never reaches the browser;"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"site"})," in ",(0,r.jsx)(n.code,{children:"astro.config.mjs"})," is ",(0,r.jsxs)(n.strong,{children:["with ",(0,r.jsx)(n.code,{children:"www"})]}),", and every canonical comes from there;"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"vercel.json"})," carries the security headers (CSP, HSTS, nosniff, Referrer-Policy,\nPermissions-Policy) and the CDN cache."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["And the gotcha that costs everyone their first hour: ",(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"src/pages/index.astro"})," IS the\ngame"]}),", served at the ",(0,r.jsx)(n.code,{children:"/"})," route. There is no ",(0,r.jsx)(n.code,{children:"public/index.html"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"database",children:"Database \u2014 managed Postgres, RLS and telemetry"}),"\n",(0,r.jsxs)(n.p,{children:["The ranking and the telemetry live in a managed Postgres. Schema and migrations are\nprivate (outside the repo \u2014 a security decision); the runtime only uses the envs.\noptional obfuscation that was delivered ready and ",(0,r.jsx)(n.strong,{children:"deliberately not applied"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Security does not come from hiding the ",(0,r.jsx)(n.code,{children:"anon"})," key \u2014 it is public by design, although the current\nclient does not receive it. It comes from the ",(0,r.jsx)(n.em,{children:"policies"}),", from the per-column grants and from the rate limit counted\nin Postgres (",(0,r.jsx)(n.code,{children:"src/lib/ratelimit.ts"})," + RPC ",(0,r.jsx)(n.code,{children:"rl_take"}),"), not in lambda memory."]}),"\n",(0,r.jsxs)(n.p,{children:["Player identity uses a stable UID to select the account and a token to authenticate\nthe session; the nickname is display data. Old clients and a database pending the\nprivate migration retain a temporary ",(0,r.jsx)(n.code,{children:"nickname + token"})," fallback."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"The ranking is off today"})," (",(0,r.jsx)(n.code,{children:"RANKING_ON"})," in ",(0,r.jsx)(n.code,{children:"src/lib/site.ts"}),") and was replaced by\nanonymous telemetry. It is a flag, not a removal \u2014 details in ",(0,r.jsx)(n.a,{href:"./status",children:"Current state"}),"."]}),"\n",(0,r.jsx)(n.admonition,{title:"None of this is required to run the game",type:"note",children:(0,r.jsxs)(n.p,{children:["Without the Supabase variables the site boots the same: the ranking routes respond\n",(0,r.jsx)(n.code,{children:"503 not_configured"})," and the pages show the notice. The game in ",(0,r.jsx)(n.code,{children:"public/"})," ",(0,r.jsx)(n.strong,{children:"uses none of\nthem"}),". See ",(0,r.jsx)(n.code,{children:".env.example"}),"."]})}),"\n",(0,r.jsx)(n.h2,{id:"asset-generation",children:"Asset generation \u2014 what is AI-generated, and by which service"}),"\n",(0,r.jsxs)(n.p,{children:["Almost every 3D and 2D asset in this game is ",(0,r.jsx)(n.strong,{children:"generated"}),", not hand-modeled. The real flow, not the\nhypothetical one:"]}),"\n","\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Service"}),(0,r.jsx)(n.th,{children:"What it generates"}),(0,r.jsx)(n.th,{children:"Script"}),(0,r.jsx)(n.th,{children:"Key"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"mint.gg"})," (Mint MCP)"]}),(0,r.jsx)(n.td,{children:"rigged characters, packs, animation"}),(0,r.jsxs)(n.td,{children:["MCP tools; ",(0,r.jsx)(n.code,{children:"mint-assets.json"})," records the result"]}),(0,r.jsx)(n.td,{children:"owner account via MCP"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Tripo3D"})}),(0,r.jsx)(n.td,{children:"3D props from text"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs --provider tripo"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"TRIPO_API_KEY"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Meshy"})}),(0,r.jsx)(n.td,{children:"3D props and rigging"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs --provider meshy"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"MESHY_API_KEY"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"OpenRouter"})}),(0,r.jsx)(n.td,{children:"2D art"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tools/gen-image.mjs"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"OPENROUTER_API_KEY"})})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"mint-assets.json"})," records ",(0,r.jsx)(n.strong,{children:"7 assets"})," generated through Mint (3 ",(0,r.jsx)(n.code,{children:"mint-model"})," \xb7 4 ",(0,r.jsx)(n.code,{children:"mint-asset-pack"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["API keys live in the gitignored root ",(0,r.jsx)(n.code,{children:".env"}),"; generation is offline and the game runs without them."]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["Block generated by ",(0,r.jsx)(n.code,{children:"node tools/gen-docs.mjs"}),". Source: ",(0,r.jsx)(n.code,{children:"git grep -l SDK -- tools/ | grep .mjs \xb7 mint-assets.json"})]}),"\n"]}),"\n","\n",(0,r.jsx)(n.h3,{id:"characters-mintgg",children:"Characters: mint.gg"}),"\n",(0,r.jsxs)(n.p,{children:["The playable characters are rigged GLBs generated by ",(0,r.jsx)(n.strong,{children:"Mint"})," (mint.gg), through the MCP\ntools \u2014 ",(0,r.jsx)(n.code,{children:"start_model_generation"})," with ",(0,r.jsx)(n.code,{children:"riggable_character"})," in T-pose and empty hands, then\n",(0,r.jsx)(n.code,{children:"animate_generated_model"})," to come out with a skeleton."]}),"\n",(0,r.jsx)(n.p,{children:"Two non-obvious facts that save money and rounds:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Mint's base model does not come rigged."})," The skeleton only appears at the animation step.\nThe cheap path for a new character is: generate the base \u2192 rig it with ",(0,r.jsx)(n.strong,{children:"one"})," clip \u2192\nuse its ",(0,r.jsx)(n.code,{children:"rigged_character_glb"})," \u2192 reuse the shared clips."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Meshy rigs share the same bone names"})," (",(0,r.jsx)(n.code,{children:"Hips"}),", ",(0,r.jsx)(n.code,{children:"Spine"}),", ",(0,r.jsx)(n.code,{children:"Head"}),",\n",(0,r.jsx)(n.code,{children:"RightHand"}),"\u2026), so a clip pack generated once matches by name on any rig in the\nfamily. That is why ",(0,r.jsx)(n.code,{children:"public/models/anims/"})," has shared clips and per-character clips\nat the same time, and why the ",(0,r.jsx)(n.code,{children:"index.json"})," manifest exists (",(0,r.jsx)(n.code,{children:"npm run anims"}),") \u2014 without it the\ngame requested clips from characters that do not have them and filled the console with 404s."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"mint-assets.json"})," is the record of what was generated: ",(0,r.jsx)(n.code,{children:"assetId"}),", ",(0,r.jsx)(n.code,{children:"chatUrl"})," and a note on what\nwent wrong in the previous attempt. ",(0,r.jsx)(n.strong,{children:"Without that record there is no reviewing and no regenerating"})," \u2014\nthe asset becomes a binary with no provenance sitting in the repository."]}),"\n",(0,r.jsx)(n.h3,{id:"props-3d",children:"3D props: Tripo3D and Meshy"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"tools/gen-asset.mjs"})," generates a prop from text, downloads the GLB and writes it ",(0,r.jsx)(n.strong,{children:"already optimized"})," into\n",(0,r.jsx)(n.code,{children:"public/models/props/"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'node tools/gen-asset.mjs --prompt "caixa de som de baile" --id caixa_som\nnode tools/gen-asset.mjs --provider meshy --prompt "carro tunado" --id carro_tunado\nnode tools/gen-asset.mjs --resume {const t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;r.t=function(o,n){if(1&n&&(o=this(o)),8&n)return o;if("object"==typeof o&&o){if(4&n&&o.__esModule)return o;if(16&n&&"function"==typeof o.then)return o}const c=Object.create(null);r.r(c);const a={};t=t||[null,e({}),e([]),e(e)];for(var s=2&n&&o;("object"==typeof s||"function"==typeof s)&&!~t.indexOf(s);s=e(s))Object.getOwnPropertyNames(s).forEach(e=>a[e]=()=>o[e]);return a.default=()=>o,r.d(c,a),c}})(),r.d=(e,t)=>{if(Array.isArray(t))for(var o=0;o{const t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{const e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;let t;r.t=function(o,n){if(1&n&&(o=this(o)),8&n)return o;if("object"==typeof o&&o){if(4&n&&o.__esModule)return o;if(16&n&&"function"==typeof o.then)return o}const a=Object.create(null);r.r(a);const c={};t=t||[null,e({}),e([]),e(e)];for(var s=2&n&&o;("object"==typeof s||"function"==typeof s)&&!~t.indexOf(s);s=e(s))Object.getOwnPropertyNames(s).forEach(e=>c[e]=()=>o[e]);return c.default=()=>o,r.d(a,c),a}})(),r.d=(e,t)=>{if(Array.isArray(t))for(var o=0;ogit shortlog -sn --no-merges minus the authors that are AI agents (which sign as Claude /
Claude (gauntlet …)).ruben-cytonic, Emerson Garrido, Ruben, rubenmarcus, Ruben Marcus, William Oliveira, Juan Versolato Lopes, daeeseD, matheusgb, Maná Soares, daltonfontes. Automated identities are excluded. A Git author name is not necessarily one unique person.ruben-cytonic, Ruben, Emerson Garrido, rubenmarcus, Ruben Marcus, William Oliveira, Juan Versolato Lopes, daeeseD, matheusgb, Maná Soares, daltonfontes. Automated identities are excluded. A Git author name is not necessarily one unique person.
diff --git a/public/docs/en/index.html b/public/docs/en/index.html
index 7fc3b6fe4..80c16d721 100644
--- a/public/docs/en/index.html
+++ b/public/docs/en/index.html
@@ -4,7 +4,7 @@
node tools/gen-docs.mjs. Source: git shortlog -sn --no-merges (descontando autores que são agentes)
| What | How much | Where to check |
|---|---|---|
| Game code | 31,744 lines in 44 files | git ls-files public/js/*.js | xargs wc -l |
game.js | 6,838 lines | wc -l public/js/game.js |
main.js | 2,646 lines | wc -l public/js/main.js |
| Weapons with GLB | 26 | git ls-files 'public/models/weapons/*.glb' | wc -l |
| Character GLBs | 45 | git ls-files 'public/models/characters/*.glb' | wc -l |
| Props in GLB | 108 | git ls-files 'public/models/props/*.glb' | wc -l |
| Versioned animation clips | 573 | git ls-files public/models/anims | wc -l |
| Playable characters | 44, in 5 factions | CHARACTERS array in characters.js |
| Maps in the registry | 12 | MAPS object in maps.js |
| Visual harnesses in HTML | 15 | git ls-files 'public/*.html' | wc -l |
| Harness scripts | 192 | git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l |
| Pipeline scripts | 54 | git ls-files 'tools/*.mjs' | wc -l |
| Written entry tasks | 26 | git ls-files 'docs/issues/[0-9]*.md' | wc -l |
| Version | 2.0.0-alpha.169 | public/js/version.js and package.json (match) |
| What | How much | Where to check |
|---|---|---|
| Game code | 32,001 lines in 44 files | git ls-files public/js/*.js | xargs wc -l |
game.js | 6,910 lines | wc -l public/js/game.js |
main.js | 2,698 lines | wc -l public/js/main.js |
| Weapons with GLB | 26 | git ls-files 'public/models/weapons/*.glb' | wc -l |
| Character GLBs | 45 | git ls-files 'public/models/characters/*.glb' | wc -l |
| Props in GLB | 108 | git ls-files 'public/models/props/*.glb' | wc -l |
| Versioned animation clips | 573 | git ls-files public/models/anims | wc -l |
| Playable characters | 44, in 5 factions | CHARACTERS array in characters.js |
| Maps in the registry | 12 | MAPS object in maps.js |
| Visual harnesses in HTML | 15 | git ls-files 'public/*.html' | wc -l |
| Harness scripts | 200 | git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l |
| Pipeline scripts | 54 | git ls-files 'tools/*.mjs' | wc -l |
| Written entry tasks | 26 | git ls-files 'docs/issues/[0-9]*.md' | wc -l |
| Version | 2.0.0-alpha.179 | public/js/version.js and package.json (match) |
@@ -112,8 +112,8 @@Block generated by
node tools/gen-docs.mjs. Source:the command in the right column of each row
npm run dev # site + game (Astro, :4321) — the / route IS the game
npm run build # dist/client + dist/server
npm run eval:vm # viewmodel framing — RUN BEFORE the invariants
npm run eval:invariants # the invariants — pure node, 10-12 min
npm run eval:bots # botsim 60 s per map, fixed seeds
npm run eval:mat # material/light/fog/texture on the maps
npm run docs # regenerates the numeric blocks of this documentation
node tools/eval/serve.mjs 8123 # static server without Astro
And the two gates, with the exact list of what each one runs — straight from package.json:
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:preload eval:docsautoria
package.json has 117 scripts; the reason behind each one lives in SCRIPTS.md.
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:mutcega eval:autofix eval:deploygate eval:portaointeiro eval:wfsecret eval:comentario eval:fixture eval:preload eval:docsautoria eval:replaycam
package.json has 124 scripts; the reason behind each one lives in SCRIPTS.md.
diff --git a/public/docs/en/quality-gates/index.html b/public/docs/en/quality-gates/index.html index 4be7fa7d4..09a7d2435 100644 --- a/public/docs/en/quality-gates/index.html +++ b/public/docs/en/quality-gates/index.html @@ -4,7 +4,7 @@Block generated by
node tools/gen-docs.mjs. Source:node -p "Object.keys(require('./package.json').scripts)"
tools/eval/invariants.mjs: 2,275 lines, 65 declared invariant identifiers, with 28 declared skip() paths.tools/eval/, plus 54 pipeline scripts in tools/.tools/eval/, plus 54 pipeline scripts in tools/.KNOWN-BUGS.md.grep -o "put('[A-Z0-9_]*'" tools/eval/invariants.mjs | sort -u | wc -l
grep -o "skip('[A-Z0-9_]*'" tools/eval/invariants.mjs | sort -u | wc -l
node tools/gen-docs.mjs
from package.json, docs/package.json and the vendored Three.js itself.
-| Layer | Tool | Version |
|---|---|---|
| 3D engine (WebGL) | Three.js, vendored | r160 |
| Game | vanilla ES modules, zero build | 44 files |
| Site | Astro with SSR | ^7.1.1 |
| Hosting | Vercel adapter | ^11.0.3 |
| Database | managed Postgres (RLS; private schema) | ^2.110.7 |
| Browser checks | Playwright | ^1.62.1 |
| GLB pipeline | gltf-transform | ^4.4.1 |
| Mesh compression | meshoptimizer | ^1.2.0 |
| Images | sharp · resvg | ^0.35.3 · ^2.6.2 |
| This documentation | Docusaurus | 3.6.3 |
| CI runtime | Node | 22 |
Three.js comes from public/vendor/three.module.js. Of the scripts in tools/, 109 import Playwright, 37 import gltf-transform, and 4 import meshoptimizer.
| Layer | Tool | Version |
|---|---|---|
| 3D engine (WebGL) | Three.js, vendored | r160 |
| Game | vanilla ES modules, zero build | 44 files |
| Site | Astro with SSR | ^7.1.1 |
| Hosting | Vercel adapter | ^11.0.6 |
| Database | managed Postgres (RLS; private schema) | ^2.110.7 |
| Browser checks | Playwright | ^1.62.1 |
| GLB pipeline | gltf-transform | ^4.4.1 |
| Mesh compression | meshoptimizer | ^1.2.0 |
| Images | sharp · resvg | ^0.35.3 · ^2.6.2 |
| This documentation | Docusaurus | 3.6.3 |
| CI runtime | Node | 22 |
Three.js comes from public/vendor/three.module.js. Of the scripts in tools/, 110 import Playwright, 37 import gltf-transform, and 4 import meshoptimizer.
diff --git a/public/docs/en/status/index.html b/public/docs/en/status/index.html index 921306590..6a8525c5b 100644 --- a/public/docs/en/status/index.html +++ b/public/docs/en/status/index.html @@ -4,7 +4,7 @@Block generated by
node tools/gen-docs.mjs. Source:dependencies/devDependencies do package.json · REVISION de public/vendor/three.module.js
| O que | Quanto | Onde confere |
|---|---|---|
| Código do jogo | 31.744 linhas em 44 arquivos | git ls-files public/js/*.js | xargs wc -l |
game.js | 6.838 linhas | wc -l public/js/game.js |
main.js | 2.646 linhas | wc -l public/js/main.js |
| Armas com GLB | 26 | git ls-files 'public/models/weapons/*.glb' | wc -l |
| GLBs de personagem | 45 | git ls-files 'public/models/characters/*.glb' | wc -l |
| Props em GLB | 108 | git ls-files 'public/models/props/*.glb' | wc -l |
| Clipes de animação versionados | 573 | git ls-files public/models/anims | wc -l |
| Personagens jogáveis | 44, em 5 facções | array CHARACTERS de characters.js |
| Mapas no registro | 12 | objeto MAPS de maps.js |
| Arnêses visuais em HTML | 15 | git ls-files 'public/*.html' | wc -l |
| Scripts do arnês | 192 | git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l |
| Scripts de pipeline | 54 | git ls-files 'tools/*.mjs' | wc -l |
| Tarefas de entrada escritas | 26 | git ls-files 'docs/issues/[0-9]*.md' | wc -l |
| Versão | 2.0.0-alpha.169 | public/js/version.js e package.json (batem) |
| O que | Quanto | Onde confere |
|---|---|---|
| Código do jogo | 32.001 linhas em 44 arquivos | git ls-files public/js/*.js | xargs wc -l |
game.js | 6.910 linhas | wc -l public/js/game.js |
main.js | 2.698 linhas | wc -l public/js/main.js |
| Armas com GLB | 26 | git ls-files 'public/models/weapons/*.glb' | wc -l |
| GLBs de personagem | 45 | git ls-files 'public/models/characters/*.glb' | wc -l |
| Props em GLB | 108 | git ls-files 'public/models/props/*.glb' | wc -l |
| Clipes de animação versionados | 573 | git ls-files public/models/anims | wc -l |
| Personagens jogáveis | 44, em 5 facções | array CHARACTERS de characters.js |
| Mapas no registro | 12 | objeto MAPS de maps.js |
| Arnêses visuais em HTML | 15 | git ls-files 'public/*.html' | wc -l |
| Scripts do arnês | 200 | git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l |
| Scripts de pipeline | 54 | git ls-files 'tools/*.mjs' | wc -l |
| Tarefas de entrada escritas | 26 | git ls-files 'docs/issues/[0-9]*.md' | wc -l |
| Versão | 2.0.0-alpha.179 | public/js/version.js e package.json (batem) |
@@ -110,8 +110,8 @@Bloco gerado por
node tools/gen-docs.mjs. Fonte:o comando da coluna direita de cada linha
npm run dev # site + jogo (Astro, :4321) — a rota / JÁ É o jogo
npm run build # dist/client + dist/server
npm run eval:vm # enquadramento do viewmodel — RODE ANTES das invariantes
npm run eval:invariants # as invariantes — node puro, 10-12 min
npm run eval:bots # botsim 60 s por mapa, sementes fixas
npm run eval:mat # material/luz/fog/textura nos mapas
npm run docs # regenera os blocos numéricos desta documentação
node tools/eval/serve.mjs 8123 # servidor estático sem Astro
E os dois quality gates, com a lista exata do que cada um roda — direto do package.json:
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:preload eval:docsautoria
package.json tem 117 scripts; o motivo de cada um mora em SCRIPTS.md (migrado das chaves //nome em 18/08/2026) — é onde está o porquê.
npm run check:fast # node tools/eval/runner.mjs syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:mutcega eval:autofix eval:deploygate eval:portaointeiro eval:wfsecret eval:comentario eval:fixture eval:preload eval:docsautoria eval:replaycam
package.json tem 124 scripts; o motivo de cada um mora em SCRIPTS.md (migrado das chaves //nome em 18/08/2026) — é onde está o porquê.
diff --git a/public/docs/instrumentacao-ai/index.html b/public/docs/instrumentacao-ai/index.html index c4b1d4846..e1e92b5c8 100644 --- a/public/docs/instrumentacao-ai/index.html +++ b/public/docs/instrumentacao-ai/index.html @@ -4,7 +4,7 @@Bloco gerado por
node tools/gen-docs.mjs. Fonte:node -p "Object.keys(require('./package.json').scripts)"
tools/eval/invariants.mjs: 2.275 linhas, 65 identificadores de invariante declarados (put()), dos quais 28 têm caminho de skip() declarado.tools/eval/ (.mjs + .py), mais 54 scripts de pipeline em tools/.tools/eval/ (.mjs + .py), mais 54 scripts de pipeline em tools/.KNOWN-BUGS.md, atualizado com saída real.Reproduza:
diff --git a/public/docs/stack/index.html b/public/docs/stack/index.html index 6c576e1e2..4d7b3c616 100644 --- a/public/docs/stack/index.html +++ b/public/docs/stack/index.html @@ -4,7 +4,7 @@node tools/gen-docs.mjs
a partir do package.json, do docs/package.json e do próprio Three.js vendorizado.
-| Camada | Ferramenta | Versão |
|---|---|---|
| Motor 3D (WebGL) | Three.js, vendorizado | r160 |
| Jogo | ES modules vanilla, zero build | 44 arquivos |
| Site | Astro com SSR | ^7.1.1 |
| Hospedagem | adapter Vercel | ^11.0.3 |
| Banco | Postgres gerenciado (RLS; schema privado, fora do repo) | ^2.110.7 |
| Browser nas réguas | Playwright | ^1.62.1 |
| Pipeline de GLB | gltf-transform | ^4.4.1 |
| Compressão de malha | meshoptimizer | ^1.2.0 |
| Imagem (build e API) | sharp · resvg | ^0.35.3 · ^2.6.2 |
| Esta documentação | Docusaurus | 3.6.3 |
| Runtime de CI | Node | 22 |
Three.js sai de public/vendor/three.module.js (sem CDN, sem npm no runtime). Astro e Vercel de package.json + astro.config.mjs + vercel.json. Dos scripts de tools/, 109 importam Playwright, 37 importam gltf-transform e 4 importam meshoptimizer.
| Camada | Ferramenta | Versão |
|---|---|---|
| Motor 3D (WebGL) | Three.js, vendorizado | r160 |
| Jogo | ES modules vanilla, zero build | 44 arquivos |
| Site | Astro com SSR | ^7.1.1 |
| Hospedagem | adapter Vercel | ^11.0.6 |
| Banco | Postgres gerenciado (RLS; schema privado, fora do repo) | ^2.110.7 |
| Browser nas réguas | Playwright | ^1.62.1 |
| Pipeline de GLB | gltf-transform | ^4.4.1 |
| Compressão de malha | meshoptimizer | ^1.2.0 |
| Imagem (build e API) | sharp · resvg | ^0.35.3 · ^2.6.2 |
| Esta documentação | Docusaurus | 3.6.3 |
| Runtime de CI | Node | 22 |
Three.js sai de public/vendor/three.module.js (sem CDN, sem npm no runtime). Astro e Vercel de package.json + astro.config.mjs + vercel.json. Dos scripts de tools/, 110 importam Playwright, 37 importam gltf-transform e 4 importam meshoptimizer.
diff --git a/public/img/map-previews/parque_treta.jpg b/public/img/map-previews/parque_treta.jpg index 0ccf5a236..5ab549806 100644 Binary files a/public/img/map-previews/parque_treta.jpg and b/public/img/map-previews/parque_treta.jpg differ diff --git a/public/img/map-previews/penitenciaria.jpg b/public/img/map-previews/penitenciaria.jpg index c00ed2300..994d1003b 100644 Binary files a/public/img/map-previews/penitenciaria.jpg and b/public/img/map-previews/penitenciaria.jpg differ diff --git a/public/img/map-previews/velho_oeste.jpg b/public/img/map-previews/velho_oeste.jpg index ed6df90c1..fb268e776 100644 Binary files a/public/img/map-previews/velho_oeste.jpg and b/public/img/map-previews/velho_oeste.jpg differ diff --git a/public/js/game.js b/public/js/game.js index 2a070654b..c9d32beeb 100644 --- a/public/js/game.js +++ b/public/js/game.js @@ -16,17 +16,11 @@ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { frase, tr } from './i18n.js'; // EN por camada — o crash 'frase is not defined' de 06/08 foi este import faltando // cor de facção: UMA origem, importada também por brasoes.js e characters.js. O espelho // que este import substitui apagou a bandeira do jogador em 07/08 — ver paleta.js. -// A origem é o registro do elenco: `paleta.js` nasceu no mesmo conserto mas só cobre as 5 -// primeiras facções, e os hexes das 5 são idênticos nos dois módulos. -import { factionColor, factionInk, factionName, factionTag } from './factions.js'; -// ESPELHO não é cor de facção: é o roxo do inimigo da MESMA facção do jogador. Continua -// vindo de paleta.js, que é onde a main o tirou dos literais espalhados por aqui. -import { ESPELHO } from './paleta.js'; +import { tons, ESPELHO } from './paleta.js'; import { PlayerRecorder } from './botbrain/recorder.js'; // BOTBRAIN: grava (estado→ação) do jogador (só quando recordTraining) import { buildState } from './botbrain/features.js'; // BOTBRAIN: monta o vetor de estado do bot p/ a rede import { sense } from './botbrain/sense.js'; // BOTBRAIN: percepção (jogo→features) import { BotBrain } from './botbrain/brain.js'; // BOTBRAIN: inferência (rede treinada rodando no bot) -import { createSoundscape } from './soundscape.js'; // vida 1: áudio ambiente por mapa (world.sound) import { WEAPONS } from './data/weapons.js'; // Reexporta pra não quebrar quem já consumia a tabela daqui: server/room.js (servidor @@ -36,8 +30,17 @@ export { WEAPONS }; ?pace=0 -> round volta a ser SÓ tempo (sem alvo de abates, sem match point) ?move=0 -> movimento volta ao modelo antigo (4.7 base, sprint 6.6, sem counter-strafe) ?killcam=0 -> sem painel/câmera de morte + ?replaycam=0 -> sem replay cam ao matar (câmera orbital na vítima) Motivo: as três mudam COMPORTAMENTO sentido pelo jogador; o dono precisa do A/B. */ const QS = new URLSearchParams(location.search); +const REPLAY_CAM = QS.get('replaycam') !== '0'; +/* Replay cam (kill-switch ?replaycam=0): duração total em s, escala de dt do hit-stop e a + janela dele em tempo real, e o raio/altura da órbita em torno da vítima. */ +const REPLAY_DUR = 1.2; +const REPLAY_SLOWMO = 0.18; +const REPLAY_SLOWMO_DUR = 0.2; +const REPLAY_ORBIT_R = 3.2; +const REPLAY_ORBIT_H = 1.8; // ?vmlab=1 usa o viewmodel afinado; sem a flag mantém o calibrado. const VMLAB = QS.get('vmlab') === '1'; /* KILL-SWITCH DA RODADA DE MATERIAL: ?vmmat=legacy devolve, de uma vez, o clamp @@ -252,21 +255,6 @@ const MOVE_MUL = { }; const WALK_MUL = 0.52; // Shift: 52% da velocidade, sem som de passo const MOVE2 = QS.get('move') !== '0'; -/* DEGRAU do corpo. Estava declarado DENTRO do `_updatePlayer` e o mantle precisa do mesmo - número — duplicar seria o instrumento discordando de si (é 0,55 que separa "degrau" de - "beirada", e os dois lados dessa fronteira têm que ler a MESMA constante). */ -const STEP_H = 0.55; -/* MANTLING — subir em beirada com as mãos. Procedência de cada número e o caso do canal - do Córrego: cabeçalho de tools/eval/mantle-check.mjs. Kill-switch: `?mantle=0`. */ -const MANTLE = QS.get('mantle') !== '0'; -const MANTLE_H = 1.95; -const MANTLE_D0 = 0.58; -const MANTLE_ALC = 1.50; -const MANTLE_APOIO = 0.50; -/* MANTLE_GRID = 0,75 m — passo da grade de alcance (ver `_mantleAlcance`). Menor que o - DIÂMETRO do corpo (0,76 m): a inundação não escorre por uma fresta por onde o corpo não - passaria. Maior que isso e ela vazaria; muito menor e custa tempo à toa. */ -const MANTLE_GRID = 0.75; /* KILL-SWITCH DO ARMÁRIO DO SPAWN (P3, 01/08). `?rack=old` traz de volta o layout cego (2 fileiras fixas a 2,0/3,25 m atrás do spawn, x absoluto) E a seleção antiga (só a arma mais próxima em 1,9 m) — os dois juntos são o bug que o dono reportou às 20:38, e ficam @@ -301,6 +289,7 @@ const RACK_RETA = QS.get('rackreta') === '1'; A simetria é parte do desenho: vale pra jogador E bots — meia regeneração faria o bot virar esponja. Régua: invariante REGEN de `tools/eval/regen-check.mjs`. */ const REGEN = QS.get('regen') === '1', REGEN_DELAY = 6, REGEN_RATE = 22; +const TEAM_LABEL = { E: 'TIME E', B: 'TIME B' }; const RADIO = { z: { title: 'COMANDOS', items: ['Bora, bora, bora!', 'Cobre eu!', 'Recua, recua!'] }, x: { title: 'RESPOSTAS', items: ['Recebido!', 'Negativo!', 'Bonito tiro!'] }, @@ -313,12 +302,6 @@ const MK_LABELS = { doublekill: 'DOUBLE KILL', triplekill: 'TRIPLE KILL', multik caixa, sem padrão, sem falloff). Existe porque isto muda o COMPORTAMENTO de mira das 26 armas de uma vez — se algo ficar ruim em produção o dono tem o A/B na querystring. */ const GUNFEEL = new URLSearchParams(location.search).get('gunfeel') !== '0'; -/* PUNCH (dono, 17/08: "feel fraco, sem punch"): concussão POR DISPARO que não mexe em - balística nem no recuo medido (vm-kick-sim) — pulso de camera.zoom (~1,4%, decai em - ~80 ms), flash/luz de boca ~30% maiores e duck do mix no tiro synth. ?punch=0 desliga. */ -const PUNCH = new URLSearchParams(location.search).get('punch') !== '0'; -const PUNCH_ZOOM = 0.014, PUNCH_DECAY = 13; -const TRACER_STYLE = Object.freeze({ radius: .0115, travel: .044, fade: .014, segment: 1.45 }); // Kill-switch: ?blood=0 desliga o sangue (spray + mancha em parede/chão + poça sob o // cadáver). Muda o "gore" sentido pelo jogador — flag pro A/B do dono, como ?gunfeel=0. const BLOOD = new URLSearchParams(location.search).get('blood') !== '0'; @@ -585,9 +568,25 @@ export function pickMatchRoster(playerFaction, enemyFaction, teamSize, playerCha }; } +/* Pool dos bots em modo `all`: uma fonte só para o sorteio da partida (pickMatchWeapons) e + para o fallback do Game — duas listas divergiriam em silêncio e furariam o preload. */ +const BOT_WEAPON_POOL = ['awp', 'ak', 'm4', 'mp5', 'shotgun', 'deagle', 'm92', 'akm', 'md97', + 'carbine', 'm400', 'mosin', 'rem700', 'lmg', 'scar', 'g3', 'tavor', 'famas', 'uzi', 'p90', 'revolver38']; + +/* Armas da partida sorteadas ANTES do preload, pelo mesmo motivo do roster: o main.js precisa + saber o que carregar, e re-sortear no Game poria arma de caixa em bot. Régua: ARM1. */ +export function pickMatchWeapons({ mode = 'all', teamSize = 8 } = {}) { + const um = () => (mode === 'awp' ? 'awp' : mode === 'knife' ? 'knife' + : mode === 'pistols' ? (Math.random() < 0.5 ? 'pistol' : 'deagle') + : BOT_WEAPON_POOL[(Math.random() * BOT_WEAPON_POOL.length) | 0]); + return Array.from({ length: Math.max(1, teamSize) * 2 }, um); +} + export class Game { - constructor({ renderer, textures, sfx, settings, playerCharId, playerTeam, playerFaction, enemyFaction, nickname, mapId, ctf, roundsMax, testMode = false, mobile = false, matchRoster = null, onQuit, onMatchEnd, onTrainingFrames, recordTraining = false }) { + constructor({ renderer, textures, sfx, settings, playerCharId, playerTeam, playerFaction, enemyFaction, nickname, mapId, ctf, roundsMax, testMode = false, mobile = false, matchRoster = null, matchWeapons = null, onQuit, onMatchEnd, onTrainingFrames, recordTraining = false }) { this._ctfOpt = ctf; + this._matchWeapons = matchWeapons; + this._armaN = 0; this.renderer = renderer; this.sfx = sfx; this.settings = settings; @@ -736,7 +735,9 @@ export class Game { // tem elenco — ver pickMatchRoster) vem sorteado do main.js ou é sorteado aqui (arnês/testes). const { allyDefs, enemyDefs } = matchRoster || pickMatchRoster(this.playerFaction, this.enemyFaction, teamSize, playerCharId); const mkBot = (def, team, i) => { - const wpn = this._botWeapon(); + // arma sorteada no main.js (que preloadou por ela); sem lista, sorteia aqui. Contador + // próprio: `i` reinicia por LADO e daria a mesma arma aos dois times. + const wpn = this._matchWeapons?.[this._armaN++] || this._botWeapon(); const c = buildCharacterModel(def, { weaponId: wpn }) || buildCharacter(def); c.group.traverse(o => { o.userData.botOwner = null; }); const bot = { @@ -1038,7 +1039,7 @@ export class Game { // tracer mesh pool (shared unit geometry + material; reused, never disposed per shot). // Estilo Claude-of-Duty (fx/tracers.js): rastro FINO branco-quente que VIAJA da boca ao // alvo e some em ~50-60ms — projétil passando, não "raio laser" amarelo persistente. - this._tracerGeo = new THREE.CylinderGeometry(TRACER_STYLE.radius, TRACER_STYLE.radius, 1, 5, 1, true); + this._tracerGeo = new THREE.CylinderGeometry(0.0035, 0.0035, 1, 5, 1, true); // fino (era 0.0065 — "lightsaber branca" em cena clara) this._tracerMat = new THREE.MeshBasicMaterial({ color: 0xfff3d6, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false }); this._tracerPool = []; // Pose de ADS (iron-sight) POR CLASSE (R7.5): delta aplicado ao vm.root conforme adsF @@ -1099,11 +1100,6 @@ export class Game { this.roundCaps = { E: 0, B: 0 }; // capturas DESTA rodada (o ctfCaps é da partida toda) this.matchKills = { E: 0, B: 0 }; // abates das rodadas JÁ FECHADAS (desempate do _endMatch) this.timeLeft = ROUND_TIME; - /* MODO ARENA: mata-mata onde quem morre troca de time. O jogo corre sem rounds, - respawn contínuo, e a partida acaba quando um time fica com ≤ 1 jogador. - ?arena=1 ativa. */ - this.arena = QS.get('arena') === '1'; - this.arenaSwitched = 0; // contador de trocas (pro HUD) /* game.js:944 — RELÓGIO DE PARTIDA DO CAPTURA (não é relógio de round). Só o modo CTF usa; no modo de abate fica Infinity e nada o lê. Ele NÃO reinicia a cada rodada (é o que o diferencia do `timeLeft`) e só aparece no HUD nos últimos @@ -1234,7 +1230,7 @@ export class Game { const pal = (pdef && pdef.pal) || { skin: 0xd9a066, shirt: 0x3a4a5a }; // LUVA POR TIME no fallback procedural também (mãos genéricas por time — pedido do dono): // P vermelho, B verde, U roxo; blend 55% (igual ao fparms) pra não virar luva plástica. - const GLOVE = { E: 0xd83232, B: 0x28c858, U: 0x8a3ffc, C: 0xf0f0f0, F: 0xffc233, M: 0x9d4edd }; + const GLOVE = { E: 0xd83232, B: 0x28c858, U: 0x8a3ffc }; const skinMat = dark(pal.skin); if (GLOVE[this.playerFaction]) skinMat.color.lerp(new THREE.Color(GLOVE[this.playerFaction]), 0.85); const sleeveMat = dark(pal.shirt); @@ -2111,138 +2107,15 @@ export class Game { const cat = RADIO[this.radioOpen]; const item = cat.items[n - 1]; if (!item) return; - // O primeiro comando é também um ping de ROTA. Ele desenha só o caminho já - // percorrido — nunca posição inimiga — e dá ao Motoca um uso concreto para a carga. - const routeSecs = this.radioOpen === 'z' && n === 1 ? this._routePing() : 0; - this.sfx.characterVoice(this.playerCharId, 'radio', { fallbackFaction: this._voiceKey(this.playerTeam), interrupt: true }); + this.sfx.radioVoice(this._voiceKey(this.playerTeam)); const log = document.createElement('div'); log.className = 'radio-line'; - log.textContent = `${this.player.name} (${tr('RÁDIO')}): ${item}` + - (routeSecs ? ` · ROTA ${routeSecs.toFixed(0)}s` : ''); + log.textContent = `${this.player.name} (${tr('RÁDIO')}): ${item}`; this.el.radioLog.appendChild(log); setTimeout(() => log.remove(), 4200); while (this.el.radioLog.children.length > 3) this.el.radioLog.firstChild.remove(); } - /* ================= mecânicas das vertical slices ================= - Helpers pequenos e testáveis: a ficha vive no motor sem espalhar condicionais de - personagem por dano, movimento, rádio e CTF. Nenhum deles altera dano ou velocidade. */ - _abilityNotice(text, life = 3200) { - if (!this.el?.radioLog) return; - const log = document.createElement('div'); - log.className = 'radio-line ability-line'; - log.textContent = text; - this.el.radioLog.appendChild(log); - setTimeout(() => log.remove(), life); - while (this.el.radioLog.children.length > 3) this.el.radioLog.firstChild.remove(); - } - - _resetSliceAbilities() { - const p = this.player; - p._motocaRun = 0; p._motocaPingReady = false; - p._pieceReady = this.playerCharId === 'doidinho-bairro'; p._pieceObjectiveId = null; - p._routeTrail = [{ x: p.pos.x, z: p.pos.z }]; - p._stackBearing = null; p._stackUntil = 0; - this._stackTraceEvent = null; - } - - _stackTrace(attacker) { - const p = this.player; - if (this.playerCharId !== 'programador-virado' || !p.alive || p.hp <= 0 || !attacker?.alive || !attacker.pos) return false; - const from = p.pos.clone(); from.y += 1.35; - const to = attacker.pos.clone(); to.y += 1.35; - // Agressor atrás de parede/fumaça não vira informação de time. Granadas podem causar - // dano sem LOS, por isso esta guarda não é redundante com o hitscan. - if (!this._losClear(from, to)) return false; - const STEP = Math.PI / 4; - const raw = Math.atan2(attacker.pos.x - p.pos.x, attacker.pos.z - p.pos.z); - const bearing = Math.round(raw / STEP) * STEP; // oitante, nunca coordenada - const until = this.time + 1.2; - let allies = 0; - for (const b of this.bots) { - if (!b.alive || b.team !== p.team || b.pos.distanceTo(p.pos) > 24) continue; - b._stackBearing = bearing; b._stackUntil = until; - // Utilidade real sem telepatia: amplia por 1,2 s a sonda normal de percepção; o bot - // ainda precisa confirmar LOS antes de adquirir/atirar e não recebe o alvo. - b.alertUntil = Math.max(b.alertUntil || 0, until); - allies++; - } - const names = ['N', 'NE', 'L', 'SE', 'S', 'SO', 'O', 'NO']; - const oct = ((Math.round(bearing / STEP) % 8) + 8) % 8; - this._stackTraceEvent = { bearing, until, allies }; - if (allies) this._abilityNotice(`STACK TRACE · ameaça a ${names[oct]} · ${allies} aliado${allies > 1 ? 's' : ''}`, 1200); - return allies > 0; - } - - _updateMotocaCharge(dt, running) { - const p = this.player; - if (this.playerCharId !== 'motoca-cachorro-loko' || p._motocaPingReady) return; - p._motocaRun = running ? (p._motocaRun || 0) + dt : 0; - if (p._motocaRun + 1e-6 >= 3) { - p._motocaRun = 3; p._motocaPingReady = true; - this._abilityNotice('ROTA CONFIRMADA · próximo ping +1s'); - } - } - - _recordRoutePoint(moving) { - const p = this.player; - p._routeTrail = p._routeTrail || []; - if (!moving) return; - const last = p._routeTrail[p._routeTrail.length - 1]; - if (!last || Math.hypot(p.pos.x - last.x, p.pos.z - last.z) >= 0.65) { - p._routeTrail.push({ x: p.pos.x, z: p.pos.z }); - if (p._routeTrail.length > 14) p._routeTrail.shift(); - } - } - - _routePing() { - const p = this.player; - const bonus = this.playerCharId === 'motoca-cachorro-loko' && p._motocaPingReady ? 1 : 0; - const duration = 2 + bonus; - if (bonus) { p._motocaPingReady = false; p._motocaRun = 0; } - const pts = (p._routeTrail?.length ? p._routeTrail : [{ x: p.pos.x, z: p.pos.z }]).slice(-10); - const group = new THREE.Group(); - const geo = new THREE.RingGeometry(0.12, 0.21, 12); - const mat = new THREE.MeshBasicMaterial({ - color: this._teamColor(p.team), transparent: true, opacity: 0.82, - depthWrite: false, side: THREE.DoubleSide, - }); - for (const q of pts) { - const ring = new THREE.Mesh(geo, mat); - const y = (this.world.groundHeightAt ? this.world.groundHeightAt(q.x, q.z) : 0) + 0.055; - ring.position.set(q.x, y, q.z); ring.rotation.x = -Math.PI / 2; group.add(ring); - } - group.userData.until = this.time + duration; group.userData.life = duration; - this.scene.add(group); - this._routePings = this._routePings || []; this._routePings.push(group); - return duration; - } - - _tickRoutePings() { - for (let i = (this._routePings || []).length - 1; i >= 0; i--) { - const g = this._routePings[i], left = g.userData.until - this.time; - const opacity = Math.max(0, Math.min(0.82, left / Math.max(0.01, g.userData.life))); - for (const m of g.children) if (m.material) m.material.opacity = opacity; - if (left > 0) continue; - this.scene.remove(g); - const first = g.children[0]; first?.geometry?.dispose?.(); first?.material?.dispose?.(); - this._routePings.splice(i, 1); - } - } - - _objectiveInteractionMultiplier(pt, solo) { - const p = this.player; - const inside = p.alive && solo === p.team && - (p.pos.x - pt.x) ** 2 + (p.pos.z - pt.z) ** 2 <= pt.r * pt.r; - if (p._pieceObjectiveId === pt.id && !inside) p._pieceObjectiveId = null; - if (this.playerCharId !== 'doidinho-bairro' || !inside) return 1; - if (p._pieceObjectiveId === pt.id) return 1.25; - if (!p._pieceReady) return 1; - p._pieceReady = false; p._pieceObjectiveId = pt.id; - this._abilityNotice('TEM UMA PEÇA PRA ISSO · objetivo 20% mais rápido'); - return 1.25; // tempo ×0,8 exige taxa ×(1/0,8), não ×1,20 - } - /* ================= flow ================= */ start() { this.el.hud.classList.remove('hidden'); @@ -2260,9 +2133,6 @@ export class Game { this.roundKills = { E: 0, B: 0 }; this.roundCaps = { E: 0, B: 0 }; this.timeLeft = ROUND_TIME; - /* MODO ARENA: sem timer de round — a partida só acaba quando um time fica - com ≤ 1 jogador (_checkArenaWin). */ - if (this.arena) this.timeLeft = Infinity; this._matchPoint = false; // banner de MATCH POINT dispara uma vez por round this._resultado = null; // o título do placar é da RODADA que acabou, não da que começa // game.js:1868 — pedido de fim de rodada do CAPTURA é POR RODADA: se a rodada acabou @@ -2310,10 +2180,9 @@ export class Game { ent.hp = 100; ent.alive = true; ent.respawnAt = 0; ent.protUntil = 0; return s; }; - const playerSpawn = place(this.player, this.playerTeam, 0); - this.player.yaw = this._spawnYaw(playerSpawn, this.playerTeam, false); + place(this.player, this.playerTeam, 0); + this.player.yaw = this.playerTeam === 'E' ? Math.PI : 0; this.player.pitch = 0; this.player.vel.set(0, 0, 0); this.player.crouchF = 0; - this._resetSliceAbilities(); this.player.ammo.awp = { mag: WEAPONS.awp.mag, res: WEAPONS.awp.reserve }; this.player.ammo.pistol = { mag: WEAPONS.pistol.mag, res: WEAPONS.pistol.reserve }; this.player.smokes = 5; this.player.frags = 1; this._updateSmokeHud(); // 5 fumaças + 1 frag por round @@ -2495,8 +2364,8 @@ export class Game { this.el.weaponName.textContent = WEAPONS[this.player.weapon].name; const slots = { E: 1, B: 0 }; for (const b of this.bots) { - const botSpawn = place(b, b.team, slots[b.team]++); - b.yaw = this._spawnYaw(botSpawn, b.team, true); // o mesh aponta para +Z + place(b, b.team, slots[b.team]++); + b.yaw = b.team === 'E' ? 0 : Math.PI; // mesh forward is +Z b.target = null; b.path = null; b.repathAt = 0; b.mesh.group.rotation.set(0, b.yaw, 0); b.mesh.group.position.copy(b.pos); @@ -2731,9 +2600,6 @@ export class Game { if (this.state !== 'live' && this.state !== 'countdown') v = false; const entrou = v && !this.paused; this.paused = v; - this.soundscape?.setPaused(v); - // reset de input na pausa: a main acrescentou o toque (controles mobile) e o botão - // do mouse; a ambiência é desta branch. Os dois lados valem. if (v) { this.keys = {}; this.touchMove.x = 0; this.touchMove.z = 0; this.mouseDown0 = false; } this.el.pause.classList.toggle('hidden', !v); // pausado, o overlay de toque some — senão a camada de OLHAR (tela cheia) tapava o menu @@ -2824,7 +2690,7 @@ export class Game { swapBot.target = null; swapBot.path = null; swapBot.hp = 100; swapBot.alive = true; const s = this.world.spawns[oldTeam][(Math.random() * 4) | 0]; swapBot.pos.set(s.x, this._spawnY(s.x, s.z), s.z); - swapBot.yaw = this._spawnYaw(s, oldTeam, true); + swapBot.yaw = oldTeam === 'E' ? 0 : Math.PI; swapBot.mesh.group.rotation.set(0, swapBot.yaw, 0); swapBot.mesh.group.position.copy(swapBot.pos); swapBot.mesh.group.visible = true; @@ -2832,7 +2698,7 @@ export class Game { // respawn do jogador no lado novo const s = this.world.spawns[newTeam][(Math.random() * 4) | 0]; p.pos.set(s.x, this._spawnY(s.x, s.z), s.z); p.vel.set(0, 0, 0); - p.yaw = this._spawnYaw(s, newTeam, false); p.pitch = 0; p.hp = 100; + p.yaw = newTeam === 'E' ? Math.PI : 0; p.pitch = 0; p.hp = 100; this._scope(false, true); this._banner(frase('agoraVoceE', this._teamName(newTeam)), 'trocou de lado na treta — sem penalty, só julgamento'); this.sfx.uiClick(); @@ -3117,9 +2983,9 @@ export class Game { : (p.weapon === 'awp' ? (p.scoped ? w.spreadScope : w.spreadHip) : w.spreadHip)) * crouchMul * moveMul; const from = this.camera.getWorldPosition(new THREE.Vector3()); const pellets = w.pellets || 1; - // Pedido do dono (17/08), "todos os tiros traçados": o rastro é segmento curto - // viajante (~50 ms), não laser contínuo — por isso o antigo 1-em-3 saiu. - const wantTracer = true; + // tracer só em PARTE dos tiros (CS): 1 em 3 na rajada; sniper/shotgun sempre (o tiro é o + // evento). Antes TODO tiro deixava rastro — vira "chuva de laser" em full-auto. + const wantTracer = !GUNFEEL || pellets > 1 || (REC_DEG[p.weapon] ?? 1) > 2.4 || ((p.sprayI || 0) % 3) === 0; for (let i = 0; i < pellets; i++) { const sp = spreadBase * (1 + this.bloom); let dir; @@ -3155,8 +3021,6 @@ export class Game { : Math.min(1.5, 0.55 + (w.recoil || 0.01) * 13); this.vm.recoil.kick(vmAmp * (1 - 0.25 * p.crouchF) * kickMul); this.vm.kickSide = Math.random() * 2 - 1; - // concussão do PUNCH: pesada em tiro único, mais leve por bala em full-auto - if (PUNCH) this._punchF = Math.min(1.4, (this._punchF || 0) + (w.auto ? 0.5 : 1) * (kickMul === 0.5 ? 0.7 : 1)); const _cls = STATIC_CLASS[p.weapon] || 'rifle'; this._flash(this._muzzleWorld(_cls), this.camera.getWorldDirection(new THREE.Vector3()), _cls); this._ejectCasing(); @@ -3219,7 +3083,6 @@ export class Game { } else { end = from.clone().add(dir.clone().multiplyScalar(120)); } - this.world.ambience?.onShot(from, end); if (byPlayer && tracer) { const muzzle = this._muzzleWorld(STATIC_CLASS[this.player.weapon] || 'rifle'); this._tracer(muzzle, end); @@ -3334,7 +3197,6 @@ export class Game { if (!ent.alive || this.state !== 'live') return; if (this.time < (ent.protUntil || 0)) return; // spawn protection: zero dano (e sem hitmarker) enquanto protegido ent.hp -= dmg; - if (ent.isPlayer && ent.hp > 0) this._stackTrace(attacker); if (ent.isPlayer) { this.el.vignette.style.opacity = 0.9; setTimeout(() => this.el.vignette.style.opacity = 0, 130); @@ -3378,12 +3240,6 @@ export class Game { ent._killT = this.time; ent.alive = false; ent.hp = 0; ent.deaths++; ent.respawnAt = this.time + RESPAWN_DELAY; - /* MODO ARENA: quem morre vai pro time do assassino. A troca é aplicada no - respawn (não aqui) pra não quebrar o killfeed nem o estado do frame atual. */ - if (this.arena && attacker && attacker.team !== ent.team) { - ent._switchTeam = attacker.team; - this.arenaSwitched++; - } // Drop tem prazo e teto porque a versão sem eles foi retirada por virar lixo de mapa; // faca não dropa (todo mundo nasce com uma). Histórico e números: tools/eval/drop-check.mjs. if (ent.weapon && ent.weapon !== 'knife' && this._pickupAllowed(ent.weapon)) { @@ -3391,9 +3247,7 @@ export class Game { } if (attacker) { attacker.kills++; this.roundKills[attacker.team]++; - // characterVoice já cai no pool da facção (fallbackFaction) sem clipe próprio; - // ele substitui o antigo sfx.voice() — chamar os dois tocaria áudio dobrado. - this.sfx.characterVoice(attacker.def?.id, 'kill', { fallbackFaction: this._voiceKey(attacker.team) }); + this.sfx.voice(this._voiceKey(attacker.team)); // killer's side celebrates (meme audio) // TELEMETRIA DE ARMA: quando o JOGADOR mata, conta a arma usada (param `weap` // já vem do _damage/_tryShoot). Bot mata não conta — não há balanço a inferir. if (attacker.isPlayer && weap) this._wperf[weap] = (this._wperf[weap] || 0) + 1; @@ -3406,6 +3260,13 @@ export class Game { mk.best = Math.max(mk.best || 0, mk.count); const kind = mk.count >= 6 ? 'godlike' : (MK_TIERS[mk.count] || (mk.life === 5 ? 'killingspree' : null)); if (kind) { this._mkBanner(MK_LABELS[kind]); this.sfx.general(kind); } + if (REPLAY_CAM && head && ent.pos) { + this._replayCam = { + t: 0, + victimPos: ent.pos.clone(), + killerYaw: attacker.yaw, + }; + } } } if (ent.isPlayer) { @@ -3426,24 +3287,6 @@ export class Game { // poça que cresce sob o cadáver (qualquer morte com posição — inclui bot×bot) if (BLOOD && ent.pos) this._bloodPoolAt(ent.pos); this._feed(attacker, ent, weap, head); - /* MODO ARENA: checa se a troca de time esvaziou um lado (≤ 1 sobrando). */ - if (this.arena) this._checkArenaWin(); - } - /* ===================== MODO ARENA: condição de vitória ===================== - Conta jogadores por time (player + bots, vivos e mortos — o morto vai - trocar no respawn). Se um time ficou com ≤ 1, o outro vence. */ - _checkArenaWin() { - const all = [this.player, ...this.bots]; - const e = all.filter(e => e.team === 'E').length; - const b = all.filter(e => e.team === 'B').length; - if (e <= 1 || b <= 1) { - const winner = e > b ? 'E' : 'B'; - this.state = 'matchEnd'; - this.el.matchEnd.classList.remove('hidden'); - this.el.matchTitle.textContent = winner === this.player.team ? 'VOCÊ SOBREVIVEU!' : 'VOCÊ FOI CONVERTIDO'; - this.el.matchSub.textContent = `Time ${this._teamTag(winner)} venceu com ${Math.max(e, b)} jogadores`; - this.el.matchStats.innerHTML = `Trocas de time: ${this.arenaSwitched} · Seus abates: ${this.player.kills}`; - } } /* ===================== INDICADOR DIRECIONAL DE DANO ===================== Dono: "matam muito fácil e o usuário não vê de onde veio o tiro". O indicador antigo era @@ -3694,14 +3537,13 @@ export class Game { t.a = (t.a || new THREE.Vector3()).copy(a); t.dir = (t.dir || new THREE.Vector3()).copy(b).sub(a).normalize(); t.dist = len; - // Segmento curto e viajante: cobre pixels suficientes para ler como bala, sem - // permanecer no quadro como um laser contínuo. - t.v = len / (GUNFEEL ? TRACER_STYLE.travel : 0.05); - t.seg = Math.min(len, GUNFEEL ? TRACER_STYLE.segment : 2.0); + // GUNFEEL: mais curto e mais rápido (CS). Era 2.0 m em 50 ms; agora 1.2 m em 32 ms — a + // bala lê como bala, não como traço luminoso pendurado no ar. + t.v = len / (GUNFEEL ? 0.032 : 0.05); + t.seg = Math.min(len, GUNFEEL ? 1.2 : 2.0); t.t = 0; - t.life = (GUNFEEL ? TRACER_STYLE.travel + TRACER_STYLE.fade : 0.062); - t.ttl = t.life; - m.material.opacity = GUNFEEL ? 0.92 : 0.9; + t.ttl = (GUNFEEL ? 0.032 : 0.05) + 0.012; // viagem + fade final — sem persistência + m.material.opacity = GUNFEEL ? 0.75 : 0.9; m.position.copy(a); m.scale.set(1, 0.01, 1); m.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), t.dir); @@ -3773,8 +3615,7 @@ export class Game { if (this._vmFlashLight) this._vmFlashLight.position.copy(off); const s = 0.85 + Math.random() * 0.45; const fxf = (this._fxTune && this._fxTune.flash) ?? 1; - const pf = PUNCH ? 1.3 : 1; - m.jetS = 0.22 * s * fxf * pf; m.coreS = 0.08 * s * fxf * pf; // boca a ~0.35m da lente: menor que o do mundo + m.jetS = 0.22 * s * fxf; m.coreS = 0.08 * s * fxf; // boca a ~0.35m da lente: menor que o do mundo m.jet.scale.setScalar(m.jetS); m.core.scale.setScalar(m.coreS); m.jetMat.rotation = Math.random() * Math.PI * 2; m.jetMat.opacity = 1; m.coreMat.opacity = 1; m.grp.visible = true; m.t = 0; @@ -3786,8 +3627,7 @@ export class Game { m.grp.position.copy(pos).addScaledVector(d, 0.05); // leve viés à frente da boca const s = 0.85 + Math.random() * 0.5; // variação por tiro (0.36–0.57m no sprite) const fxf = (this._fxTune && this._fxTune.flash) ?? 1; - const pf = PUNCH ? 1.3 : 1; - m.jetS = 0.42 * s * fxf * pf; m.coreS = 0.15 * s * fxf * pf; + m.jetS = 0.42 * s * fxf; m.coreS = 0.15 * s * fxf; m.jet.scale.setScalar(m.jetS); m.core.scale.setScalar(m.coreS); m.jetMat.rotation = Math.random() * Math.PI * 2; // estrela nunca repete o ângulo m.jetMat.opacity = 1; m.coreMat.opacity = 1; m.grp.visible = true; m.t = 0; @@ -3795,7 +3635,7 @@ export class Game { } } const l = this._mzLights.pop(); - if (l) { l.position.copy(pos).addScaledVector(d, 0.12); l.intensity = 18 * ((this._fxTune && this._fxTune.light) ?? 1) * (PUNCH ? 1.35 : 1); this._mzLightActive.push({ l, t: 0, life: 0.05 }); } + if (l) { l.position.copy(pos).addScaledVector(d, 0.12); l.intensity = 18 * ((this._fxTune && this._fxTune.light) ?? 1); this._mzLightActive.push({ l, t: 0, life: 0.05 }); } // flash na CENA DO VM: pulso breve sincronizado (ilumina a arma em 1ª pessoa) if (this._vmFlash) { this._vmFlash.t = 0; if (this._vmFlashLight) this._vmFlashLight.intensity = this._vmFlash.peak * ((this._fxTune && this._fxTune.light) ?? 1); } // faíscas 3D (partículas com velocidade, encolhendo) + fumacinha. No tiro do PRÓPRIO @@ -3834,13 +3674,13 @@ export class Game { for (let i = this.tracers.length - 1; i >= 0; i--) { const t = this.tracers[i]; t.t += dt; t.ttl -= dt; - // segmento viajante: cabeça avança a t.v, cauda segue t.seg atrás; fade pela vida total + // segmento viajante: cabeça avança a t.v, cauda segue t.seg atrás; fade nos últimos 50ms const head = Math.min(t.dist, t.v * t.t); const tail = Math.max(0, head - t.seg); const vis = Math.max(0.01, head - tail); t.m.scale.y = vis; t.m.position.copy(t.a).addScaledVector(t.dir, tail + vis * 0.5); - t.m.material.opacity = 0.92 * Math.max(0, 1 - t.t / t.life); + t.m.material.opacity = 0.9 * Math.max(0, 1 - t.t / t.ttl); // fade ao longo do trajeto if (t.ttl <= 0) { this.scene.remove(t.m); this._tracerPool.push(t); this.tracers.splice(i, 1); } } // cápsulas: gravidade + quica no chão + gira; encolhe e some no fim. @@ -3884,8 +3724,7 @@ export class Game { this._vmFlashLight.intensity = f.peak * ((this._fxTune && this._fxTune.light) ?? 1) * (1 - k) * (1 - k); } else if (this._vmFlashLight.intensity !== 0) this._vmFlashLight.intensity = 0; } - this._tickRoutePings(); - } + } _ejectCasing() { if (this.player.weapon === 'knife') return; @@ -4167,10 +4006,9 @@ export class Game { // Urbanas; senão P vermelho / B verde. `dark` = tom mais escuro (pano da bandeira). _teamColor(side, dark = false) { // o que depende de partida fica aqui (quem é espelho, que facção está de cada lado); - // a COR de cada facção mora no registro do elenco e é a mesma que bandeira e rim - // consomem. O roxo do espelho sai de ESPELHO, e não mais de literal solto aqui. - if (this._mirror(side)) return dark ? ESPELHO.escura : ESPELHO.base; - return factionColor(this._factionOf(side), dark); + // a COR de cada facção mora em paleta.js e é a mesma que bandeira e rim consomem. + const p = this._mirror(side) ? ESPELHO : tons(this._factionOf(side)); + return dark ? p.escura : p.base; } /* TINTA CLARA DO TIME — só pra TEXTO sobre chip tingido (killfeed). Por que existe: o chip do killfeed é `background:${cor}2e` (a própria cor do time a 18%) com o texto @@ -4181,18 +4019,15 @@ export class Game { style.css:521-522) — a versão PÁLIDA da cor. Aqui ela ganha nome e vale pras 6 facções. Mantém a leitura "vermelho = time-e / verde = time-b" e passa a 5,9-9,1:1. */ _teamInk(side) { - if (this._mirror(side)) return ESPELHO.palida; - return factionInk(this._factionOf(side)); + return (this._mirror(side) ? ESPELHO : tons(this._factionOf(side))).palida; } // Pack de vozes/round por FACÇÃO: o lado do jogador usa 'U' (Tribos) quando a facção é Tribos // Urbanas; senão o lado (P/B). O inimigo é sempre político. Corrige "Tribos usa voz de Time E". // Facção que ocupa um LADO físico (P/B): lado do jogador = playerFaction, o outro = enemyFaction. _factionOf(side) { return side === this.playerTeam ? this.playerFaction : this.enemyFaction; } _voiceKey(side) { return this._factionOf(side); } // pack de vozes/round por facção (P/B/U) - /* Registro de facções é a origem única de nome/sigla (10 facções) — o mapa fixo da - main cobria só 5. */ - _teamName(side) { return factionName(this._factionOf(side)); } - _teamTag(side) { return factionTag(this._factionOf(side)); } + _teamName(side) { const f = this._factionOf(side); return f === 'U' ? 'TRIBOS URBANAS' : f === 'C' ? 'PALHAÇOS' : f === 'F' ? 'FUNKEIROS' : (TEAM_LABEL[f] || f); } + _teamTag(side) { const f = this._factionOf(side); return f === 'U' ? 'TRB' : f === 'C' ? 'PLH' : f === 'F' ? 'FNK' : f === 'E' ? 'TME' : 'TMB'; } /* Uma plaqueta do HUD. Chamada por QUADRO, então tudo aqui é comparação barata: o número só é escrito se mudou, e o brasão (data-f, arte no CSS) só quando a @@ -4371,12 +4206,10 @@ export class Game { pt.capTeam = solo; // time que está capturando agora (pra cor da barra no HUD) pt.contested = np > 0 && nb > 0; if (solo && solo !== pt.owner) { - let crew = Math.min(2, 1 + 0.35 * ((solo === 'E' ? np : nb) - 1)); // 2º e 3º corpo aceleram - crew *= this._objectiveInteractionMultiplier(pt, solo); + const crew = Math.min(2, 1 + 0.35 * ((solo === 'E' ? np : nb) - 1)); // 2º e 3º corpo aceleram pt.prog += (dt * crew) / (pt.owner ? CAP_STEAL : CAP_NEUTRAL); if (pt.prog >= 1) { pt.owner = solo; pt.prog = 0; - if (this.player._pieceObjectiveId === pt.id) this.player._pieceObjectiveId = null; this.sfx.captureSound && this.sfx.captureSound(this._factionOf(solo)); // captura: pool de som por facção (palhaços = pasta própria) // credita a captura: +1 pro time e +1 pra cada combatente do time DENTRO do anel this.ctfCaps[solo] = (this.ctfCaps[solo] || 0) + 1; @@ -4389,7 +4222,6 @@ export class Game { this._updateCtfHud(); } } else if (!solo) { - this._objectiveInteractionMultiplier(pt, solo); // encerra interação abandonada/contestada // CONTESTADO (os dois times no anel) CONGELA o progresso — é o momento de tensão do // modo; só decai quando o anel fica vazio ou o dono retoma sozinho. if (!pt.contested) pt.prog = Math.max(0, pt.prog - dt / (CAP_NEUTRAL * DECAY)); @@ -4797,99 +4629,6 @@ export class Game { pos.x += ex * cs + ez * sn; pos.z += -ex * sn + ez * cs; } - /* MAPA DE ALCANCE A PÉ — trava anti-exploit: o pouso do mantle só vale sobre chão que a - caminhada já alcança. Medições e custos: cabeçalho de tools/eval/mantle-check.mjs. */ - _mantleAlcance() { - if (this._mAlc) return this._mAlc; - const W = this.world, B = W.bounds, G = MANTLE_GRID; - const nx = Math.max(1, Math.floor((B.maxX - B.minX) / G)); - const nz = Math.max(1, Math.floor((B.maxZ - B.minZ) / G)); - const alt = new Float32Array(nx * nz).fill(NaN); - const p = new THREE.Vector3(); - /* PONTO REPRESENTATIVO, não o centro: num degrau o centro da célula pode cair no único - ponto ruim e a escada inteira aparecer interrompida (caso medido no fy_lajes). */ - for (let i = 0; i < nx; i++) for (let k = 0; k < nz; k++) { - const cx = B.minX + (i + 0.5) * G, cz = B.minZ + (k + 0.5) * G; - for (const [ox, oz] of [[0, 0], [0.25, 0], [-0.25, 0], [0, 0.25], [0, -0.25]]) { - const x = cx + ox, z = cz + oz, y = W.groundHeightAt(x, z, 1e3); - p.set(x, y, z); this._collide(p, 0.38); - if (Math.abs(p.x - x) < 1e-3 && Math.abs(p.z - z) < 1e-3) { alt[i * nz + k] = y; break; } - } - } - const vis = new Uint8Array(nx * nz), fila = []; - for (const lista of Object.values(W.spawns || {})) for (const s of (lista || [])) { - const i = Math.round((s.x - B.minX) / G - 0.5), k = Math.round((s.z - B.minZ) / G - 0.5); - if (i < 0 || i >= nx || k < 0 || k >= nz) continue; - const c = i * nz + k; - if (!Number.isNaN(alt[c]) && !vis[c]) { vis[c] = 1; fila.push(c); } - } - for (let h = 0; h < fila.length; h++) { - const c = fila[h], i = (c / nz) | 0, k = c % nz; - for (const [di, dk] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) { - const j = i + di, l = k + dk; - if (j < 0 || j >= nx || l < 0 || l >= nz) continue; - const d = j * nz + l; - if (vis[d] || Number.isNaN(alt[d])) continue; - /* MESMA regra do corpo (subir só até STEP_H), conferida ao LONGO do trecho: - comparar só as pontas aliasa escada — no fy_lajes um degrau entre centros reprovava a laje. */ - const ax0 = B.minX + (i + 0.5) * G, az0 = B.minZ + (k + 0.5) * G; - let y = alt[c], ok = true; - for (let s = 1; s <= 3; s++) { - const sx = ax0 + di * G * (s / 3), sz = az0 + dk * G * (s / 3); - const sy = W.groundHeightAt(sx, sz, y + STEP_H); - if (sy - y > STEP_H) { ok = false; break; } - y = sy; - } - if (ok) { vis[d] = 1; fila.push(d); } - } - } - this._mAlc = { nx, nz, G, minX: B.minX, minZ: B.minZ, alt, vis }; - return this._mAlc; - } - /* O ponto (x, z) na cota `y` é chão que a caminhada já alcança? Olha a vizinhança de uma - célula porque a grade é grossa de propósito; a cota tem que BATER (dentro do degrau), - senão um beco alcançável a 1 m de distância aprovaria a laje que está 3,5 m acima. */ - _mantleAlcancavel(x, z, y) { - const M = this._mantleAlcance(); - const i0 = Math.round((x - M.minX) / M.G - 0.5), k0 = Math.round((z - M.minZ) / M.G - 0.5); - for (let i = i0 - 1; i <= i0 + 1; i++) for (let k = k0 - 1; k <= k0 + 1; k++) { - if (i < 0 || i >= M.nx || k < 0 || k >= M.nz) continue; - const c = i * M.nz + k; - if (M.vis[c] && Math.abs(M.alt[c] - y) <= STEP_H) return true; - } - return false; - } - /* ALVO DE MANTLE: devolve o ponto de pouso `{x, y, z}` ou null. ÚNICA autoridade da - decisão — tools/eval/mantle-check.mjs chama este método em vez de reimplementar a regra. */ - _mantleTarget(pos, dx, dz) { - if (!MANTLE) return null; - const W = this.world; - if (!W || typeof W.groundHeightAt !== 'function') return null; // degradação segura - const n = Math.hypot(dx, dz); - if (!(n > 1e-4)) return null; - dx /= n; dz /= n; - const chao = W.groundHeightAt(pos.x, pos.z, pos.y); - const teto = chao + MANTLE_H; - const p = new THREE.Vector3(); - for (let d = MANTLE_D0; d <= MANTLE_ALC + 1e-6; d += 0.25) { - const px = pos.x + dx * d, pz = pos.z + dz * d; - const py = W.groundHeightAt(px, pz, teto + 0.5); // Y do teto: resolve mapa multinível - p.set(px, py, pz); this._collide(p, 0.38); - if (Math.abs(p.x - px) > 1e-3 || Math.abs(p.z - pz) > 1e-3) continue; - if (py <= chao + STEP_H) return null; - if (py > teto) return null; - // APOIO: a superfície tem que continuar plana MANTLE_APOIO adiante, senão o corpo - // não cabe em cima e o mantle entrega o jogador a uma queda. - const ax = px + dx * MANTLE_APOIO, az = pz + dz * MANTLE_APOIO; - const ay = W.groundHeightAt(ax, az, py + 0.5); - if (Math.abs(ay - py) > STEP_H) return null; - p.set(ax, ay, az); this._collide(p, 0.38); - if (Math.abs(p.x - ax) > 1e-3 || Math.abs(p.z - az) > 1e-3) return null; - if (!this._mantleAlcancavel(ax, az, ay)) return null; // território novo: ver _mantleAlcance - return { x: ax, y: ay, z: az }; - } - return null; - } /* PONTO ANDÁVEL MAIS PRÓXIMO (usado pelo armário do spawn). Empurra (x,z) pra fora de qualquer colisor/limite usando a MESMA física do jogador — se o _collide não mexe no ponto, o jogador consegue ficar em pé nele; é essa a @@ -5022,24 +4761,50 @@ export class Game { c.rotation.x += (wantPitch - c.rotation.x) * Math.min(1, dt * 3.5); } } + _updateReplayCam(dt) { + const rc = this._replayCam; + if (!rc) return; + rc.t += dt; + if (rc.t >= REPLAY_DUR) { + this._replayCam = null; + const p = this.player; + const tFov = p.scoped ? this._zoomFov(p.weapon) : 70; + this.camera.fov = tFov; + this.camera.updateProjectionMatrix(); + this._fovFrom = undefined; + return; + } + const progress = rc.t / REPLAY_DUR; + const angle = rc.killerYaw + Math.PI + progress * 1.2; + const ease = 1 - (1 - progress) * (1 - progress); + const r = REPLAY_ORBIT_R * (0.6 + 0.4 * ease); + const cx = rc.victimPos.x + Math.sin(angle) * r; + const cz = rc.victimPos.z + Math.cos(angle) * r; + const cy = rc.victimPos.y + REPLAY_ORBIT_H - ease * 0.4; + this.camera.position.set(cx, cy, cz); + const lookY = rc.victimPos.y + 1.2; + const dx = rc.victimPos.x - cx, dz = rc.victimPos.z - cz; + this.camera.rotation.set( + Math.atan2(lookY - cy, Math.hypot(dx, dz)), + Math.atan2(-dx, -dz), + 0 + ); + this.camera.fov = 50; + this.camera.updateProjectionMatrix(); + if (this.vm?.root) this.vm.root.visible = false; + if (this.el.crosshair) this.el.crosshair.style.display = 'none'; + } _updatePlayer(dt) { const p = this.player; this._checkCtfAlvo(); // alvo de BANDEIRAS: única condição de vitória da rodada de CAPTURA (sem gate) if (PACE) this._checkPace(); // alvo de abates / match point — vale também com o jogador morto if (!p.alive) { - /* Morrer APAGA a escalada em curso. Sem isto ela retomaria depois do respawn e - teleportaria o corpo novo para a beirada onde o corpo velho morreu. */ - p.mantle = null; + if (this._replayCam) this._replayCam = null; const left = p.respawnAt - this.time; this.el.respawnCount.textContent = Math.max(0, left).toFixed(1); this._deathFeedback(dt); if (left <= 0) this._respawnPlayer(); - // Em mapa multinível, um piso global atravessa a laje do andar alto (caso: mirante - // do Escadão) — a queda da câmera termina acima do piso LOCAL onde o corpo morreu. - const deathFloor = (this.world.groundHeightAt - ? this.world.groundHeightAt(p.pos.x, p.pos.z, p.pos.y) - : 0) + 0.5; - this.camera.position.y = Math.max(deathFloor, this.camera.position.y - dt * 2); + this.camera.position.y = Math.max(0.5, this.camera.position.y - dt * 2); this.camera.rotation.z = Math.min(0.5, (this.camera.rotation.z || 0) + dt * 0.8); return; } @@ -5110,7 +4875,7 @@ export class Game { if (this.keys.Space && !this._spaceHeld) p.jumpBufferedUntil = this.time + 0.13; this._spaceHeld = !!this.keys.Space; if ((p.jumpBufferedUntil || 0) > this.time && this.time < (p.coyoteUntil || 0) && this._acceptInput()) { - p.vel.y = this.world.jumpImpulse ?? 5.0; p.grounded = false; p.jumpBufferedUntil = 0; p.coyoteUntil = 0; this.sfx.jump(); + p.vel.y = 5.0; p.grounded = false; p.jumpBufferedUntil = 0; p.coyoteUntil = 0; this.sfx.jump(); // apex ~0.61m (CoD) } p.vel.y -= 20.6 * dt; // gravidade exagerada do CoD — arco de pulo "snappy", não flutuante // integrate with step-limit so platform fronts block @@ -5123,7 +4888,7 @@ export class Game { // o corpo só era realinhado no snap de gravidade do frame seguinte — subir meio-fio/degrau // "engasgava"). Acima disso é parede: além de bloquear, ZERA a velocidade daquele eixo, // senão o jogador segue acelerando contra o degrau e a arma treme parada no obstáculo. - /* STEP_H mora no topo do módulo desde o mantle: é a MESMA fronteira degrau/beirada. */ + const STEP_H = 0.55; const tryAxis = (dx, dz, ax) => { const nx = p.pos.x + dx, nz = p.pos.z + dz; const g = this.world.groundHeightAt(nx, nz, p.pos.y); @@ -5140,24 +4905,6 @@ export class Game { if (!p.grounded && p.vel.y < -4) { this.sfx.land(); p.landDip = Math.min(1, -p.vel.y / 14); } // landing dip, sized by impact p.pos.y = g2; p.vel.y = 0; p.grounded = true; } else if (p.pos.y > g2 + 0.05) p.grounded = false; - /* MANTLING — fica DEPOIS da física de propósito: este bloco sobrescreve `p.pos` e - descarta o empurrão do `_collide` no meio da subida; reordenar reintroduz o BUG-32. */ - if (p.mantle) { - const m = p.mantle; - m.t += dt; - const u = Math.min(1, m.t / m.dur); - const uy = Math.min(1, u / 0.62), uxz = Math.max(0, (u - 0.35) / 0.65); - p.pos.set(m.x0 + (m.x1 - m.x0) * uxz, m.y0 + (m.y1 - m.y0) * uy, m.z0 + (m.z1 - m.z0) * uxz); - p.vel.set(0, 0, 0); p.grounded = false; - if (u >= 1) { p.pos.set(m.x1, m.y1, m.z1); p.grounded = true; p.mantle = null; } - } else if (MANTLE && (ix || iz)) { - const alvo = this._mantleTarget(p.pos, wx, wz); - if (alvo) { - const sobe = alvo.y - this.world.groundHeightAt(p.pos.x, p.pos.z, p.pos.y); - p.mantle = { t: 0, dur: 0.22 + 0.13 * sobe, x0: p.pos.x, y0: p.pos.y, z0: p.pos.z, x1: alvo.x, y1: alvo.y, z1: alvo.z }; - this.sfx.jump(); - } - } // auto-fire (ak/m4/mp5) enquanto o botão está segurado if (WEAPONS[p.weapon].auto && this.mouseDown0 && p.alive) this._tryShoot(); this.bloom = Math.max(0, (this.bloom || 0) - dt * 1.8); @@ -5183,9 +4930,6 @@ export class Game { this.camera.rotation.set(p.pitch + p.recoilP, p.yaw, 0); // footsteps + view bob const moving = sp > 0.6 && p.grounded; - const running = moving && !p.scoped && p.crouchF < 0.2 && slowMul === 1 && sp >= maxSp * 0.88; - this._updateMotocaCharge(dt, running); - this._recordRoutePoint(moving); if (moving) { p.stepPhase += dt * sp * 1.6; const prev = Math.sin(p.stepPhase - dt * sp * 1.6), now = Math.sin(p.stepPhase); @@ -5208,13 +4952,6 @@ export class Game { this.camera.fov += Math.sign(tFov - this.camera.fov) * Math.min(stepFov, Math.abs(tFov - this.camera.fov)); this.camera.updateProjectionMatrix(); } else { this._fovFrom = undefined; this._fovTo = tFov; } - // PUNCH: pulso de zoom por disparo, decai exponencial. Canal próprio (camera.zoom): - // não disputa com a rampa de FOV do ADS acima nem com o recuo medido do vm-kick-sim. - if (this._punchF > 0.001) { - this._punchF *= Math.exp(-PUNCH_DECAY * dt); - const z = 1 + PUNCH_ZOOM * Math.min(1, this._punchF); - if (Math.abs((this.camera.zoom || 1) - z) > 0.0004) { this.camera.zoom = z; this.camera.updateProjectionMatrix(); } - } else if (this._punchF) { this._punchF = 0; this.camera.zoom = 1; this.camera.updateProjectionMatrix(); } // LUNETA (G3-R1 — conserto da "faixa preta"). A máscara é um overlay circular de borda // escura cuja opacidade acompanha o progresso do zoom (smoothstep). O que quebrava antes // não era a máscara e sim a ORDEM: arma e crosshair sumiam no frame do clique, enquanto a @@ -5367,6 +5104,7 @@ export class Game { if (wg) poseToWeapon(this.vm.arms, wg, p.weapon); } if (VMLAB) this._vmlabFrame(p, a); // ?vmlab=1: troca pelo viewmodel do editor (isolado) + this._updateReplayCam(dt); } // piscina_treta ground weapons: anyone who runs over one grabs it (CS-1.6 style). // The gun vanishes and respawns after PICKUP_RESPAWN. No-op on maps without @@ -5530,9 +5268,7 @@ export class Game { if (mode === 'awp') return 'awp'; if (mode === 'knife') return 'knife'; if (mode === 'pistols') return Math.random() < 0.5 ? 'pistol' : 'deagle'; - const pool = ['awp', 'ak', 'm4', 'mp5', 'shotgun', 'deagle', 'm92', 'akm', 'md97', - 'carbine', 'm400', 'mosin', 'rem700', 'lmg', 'scar', 'g3', 'tavor', 'famas', 'uzi', 'p90', 'revolver38']; - return pool[(Math.random() * pool.length) | 0]; + return BOT_WEAPON_POOL[(Math.random() * BOT_WEAPON_POOL.length) | 0]; } // Modo restrito não tem pickup de outra arma no mapa, então reserva finita = partida // acabada quando zera. Fica infinita a RESERVA, não o pente: a recarga segue cobrando. @@ -5647,11 +5383,6 @@ export class Game { _spawnY(x, z) { return this.world && this.world.groundHeightAt ? this.world.groundHeightAt(x, z) : 0; } - _spawnYaw(spawn, team, bot = false) { - if (spawn && Number.isFinite(spawn.yaw)) return spawn.yaw; - // Compatibilidade com mapa antigo sem yaw: preserva as convenções anteriores. - return team === 'E' ? (bot ? 0 : Math.PI) : (bot ? Math.PI : 0); - } _pickSpawn(team) { const list = this.world.spawns[team] || []; if (!list.length) return { x: 0, z: 0 }; @@ -5677,18 +5408,13 @@ export class Game { } _respawnPlayer() { const p = this.player; - /* MODO ARENA: aplica a troca de time antes de escolher o spawn. */ - if (this.arena && p._switchTeam) { - p.team = p._switchTeam; p._switchTeam = null; - this.sfx.general('headshot'); // sting de troca - } const s = this._pickSpawn(p.team); p.pos.set(s.x, this._spawnY(s.x, s.z), s.z); p.vel.set(0, 0, 0); p.hp = 100; p.alive = true; p.crouchF = 0; p._lifeDmg = 0; if (this._deathPanel) this._deathPanel.innerHTML = ''; // painel de morte não vaza pra vida nova p.protUntil = this.time + SPAWN_PROT; - p.yaw = this._spawnYaw(s, p.team, false); p.pitch = 0; + p.yaw = p.team === 'E' ? Math.PI : 0; p.pitch = 0; // Top off the CURRENT loadout's mags (primary could be any weapon now, not just AWP). // #268: em modo arma-única só recarrega slot PERMITIDO pelo modo (pistola não sai de // 0/0 no SÓ AWP), e quem morreu com arma proibida renasce com a arma do modo. @@ -5741,7 +5467,7 @@ export class Game { this.el.radioLog.appendChild(log); setTimeout(() => log.remove(), 3600); while (this.el.radioLog.children.length > 3) this.el.radioLog.firstChild.remove(); - try { this.sfx.characterVoice(b.def?.id, 'radio', { fallbackFaction: this._voiceKey(b.team) }); } catch {} + try { this.sfx.radioVoice(this._voiceKey(b.team)); } catch {} } /* ===================== MARCADOR DE TIME (halo + chevron) ===================== Dono: "ia ser legal se tivesse um halo no chão, ou uma seta em cima deles mostrando que @@ -5852,8 +5578,6 @@ export class Game { g.position.y = b.pos.y + Math.max(-0.6, 0 - b.deadT * 0.3); } if (this.time >= b.respawnAt && (this.state === 'live')) { - /* MODO ARENA: bot troca de time antes do respawn. */ - if (this.arena && b._switchTeam) { b.team = b._switchTeam; b._switchTeam = null; } const s = this._pickSpawn(b.team); // mesmo critério de segurança do jogador b.pos.set(s.x, this._spawnY(s.x, s.z), s.z); b.hp = 100; b.alive = true; /* RENASCER NO MESMO PIXEL: o _pickSpawn devolve o ponto MAIS SEGURO, e ele é o mesmo @@ -5867,7 +5591,7 @@ export class Game { b.mag = (WEAPONS[b.weapon] && WEAPONS[b.weapon].mag) || 30; b.aimErr = 0.2; b.burst = 0; b.alertUntil = 0; b._hurtAt = 0; b.reloadUntil = 0; b.focusUntil = 0; b._spinAcc = 0; b._spinAt = 0; b._sideUntil = 0; // estado de mira/anti-pirueta da vida anterior - b.target = null; b.path = null; b.yaw = this._spawnYaw(s, b.team, true); + b.target = null; b.path = null; b.yaw = b.team === 'E' ? 0 : Math.PI; b.laneX = undefined; b.roamUntil = 0; // re-sorteia a coluna A CADA VIDA -> rotas variam (não "sempre a mesma") b._banNodes = null; b._unreach = null; b._escapeUntil = 0; b._jukeAt = 0; // limpa estado de rota/juke da vida anterior (G2-R6A) // rumo suavizado e turno de duelo também são estado de vida: nascer com o _hdg da @@ -6751,7 +6475,6 @@ export class Game { const hw = this.ray.intersectObjects(this.world.occluders, false)[0]; if (hw && hw.distance < tdist - 0.4) hit = false; // parede na frente: bala morre lá const end = hit ? teye : (hw ? hw.point : from.clone().add(dir.clone().multiplyScalar(120))); - this.world.ambience?.onShot(from, end); if (hit) { let dmg = (Wb.dmg || 30) * (Wb.pellets ? Math.min(Wb.pellets, 6) * 0.55 : 1); const fo = DMG_FALLOFF[bcls]; @@ -6928,7 +6651,6 @@ export class Game { cada um"). O brasão é o MESMO arquivo que estampa a bandeira CTF (img/brasoes/), lido pela letra da facção que ocupa o lado — nunca pelo lado cru (a lição do _factionOf: lado 'B' ≠ facção 'B' por acidente de letra). */ - const coluna = (side) => { const linhas = [...this.combatants].filter(c => c.team === side).sort(rank).map((c, i) => { const score = Math.max(0, c.kills * 100 + (c.captures || 0) * 250 - c.deaths * 20); @@ -7075,6 +6797,11 @@ export class Game { /* ================= main update ================= */ update(dt, render = true) { if (this.paused) return; + // Hit-stop: scale dt during replay cam slowmo phase (uses wall-clock time, not game time) + if (this._replayCam) { + const wallT = this._replayCam._wallT = (this._replayCam._wallT || 0) + dt; + if (wallT < REPLAY_SLOWMO_DUR) dt *= REPLAY_SLOWMO; + } this.time += dt; if (this.state === 'countdown' && this.time >= this.stateUntil) { this.state = 'live'; @@ -7140,9 +6867,6 @@ export class Game { r.autoClear = true; } this._tickDolly(dt); - this.world.ambience?.update(dt, this.player.pos); - if (!this.soundscape && this.world.sound) this.soundscape = createSoundscape(this.sfx, this.world.sound); - this.soundscape?.update(dt, this.player.pos); this.world.update?.(dt, this.time); } @@ -7180,8 +6904,6 @@ export class Game { this.el.scoreboard.classList.add('hidden'); this.el.vignette.style.opacity = 0; if (this._dolly) { this._dolly.renderer.dispose(); this._dolly.canvas.remove(); this._dolly = null; } - this.world.ambience?.dispose(); - this.soundscape?.dispose(); this.soundscape = null; this.scene.traverse(o => { if (o.geometry) o.geometry.dispose(); }); this.scene.clear(); } diff --git a/public/js/glbchars.js b/public/js/glbchars.js index 1e2bef306..c0dfc943e 100644 --- a/public/js/glbchars.js +++ b/public/js/glbchars.js @@ -39,21 +39,12 @@ export const GLB_CHARS = new Set([ // os outros 8 são GLBs Mint riggados offline (tools/rig-from-donor.mjs, esqueleto do mst) // com clips retargetados em models/anims/Bloco gerado por
node tools/gen-docs.mjs. Fonte:dependencies/devDependencies do package.json · REVISION de public/vendor/three.module.js