Skip to content

perf(anims): compressão meshopt dos clipes — 35,6 MB viram 13,9 MB - #413

Closed
rubenmarcus wants to merge 5 commits into
mainfrom
perf/anims-meshopt
Closed

perf(anims): compressão meshopt dos clipes — 35,6 MB viram 13,9 MB#413
rubenmarcus wants to merge 5 commits into
mainfrom
perf/anims-meshopt

Conversation

@rubenmarcus

Copy link
Copy Markdown
Member

Summary

  • models/anims (570 arquivos, 35,6 MB) era a maior pasta de modelo do jogo e a única sem otimizador; ganhou um (optimize-anims.mjs);
  • decodificador meshopt vendorizado (28 KB) e registrado nos três carregadores — sem ele o GLB comprimido não abre;
  • régua eval:animsdesvio no check:fast, com a conversão de unidade que quase me enganou.

Evidência

antes depois
models/anims 35,6 MB 13,9 MB (-61%)

Não há malha skinned aqui (27,5 dos 35,6 MB são keyframe puro), então o veto do optimize-tribos a quantizar malha não se aplica. O que se aplica é medir o desvio.

O pré-requisito que quase passou batido

O jogo usava new GLTFLoader() sem decodificador nenhum. GLB com EXT_meshopt_compression simplesmente não abriria — e o catch por arquivo faria isso sumir calado, virando boneco procedural. O decoder vai vendorizado e registrado em glbchars, weapons e fparms; o tools/merge-anims.mjs também precisou dele (estourava com Missing required extension).

Desvio medido — e a armadilha de unidade

O rig do Mixamo é autorado em centímetros, e o nó Armature carrega escala 0,01. Medir o canal cru e chamar de milímetro infla o número por 100: minha primeira leitura deu "42 mm" quando o desvio real é 0,42 mm. A régua AD converte pela escala do Armature, que é o que o jogo aplica.

medido teto
AD1 translação 0,420 mm 2 mm
AD2 rotação 0,829° 1,5°

Os 0,83° de rotação são o número a observar: num antebraço de 30 cm dão ~4 mm na mão. Em terceira pessoa é invisível; na primeira pessoa o IK de mão (poseToWeapon) reencosta a mão na arma todo quadro.

Validation

  • npm run check:fast — 58/58
  • anims:check e anims:merge:check verdes
  • boneco continua animando no navegador (osso deslocou 2,9 m em 1,5 s)
  • mutante teto-frouxo acende AD1 e AD2

Risk

  • medium — 570 ativos de runtime reescritos + decodificador novo no caminho de carga

rubenmarcus and others added 4 commits August 22, 2026 00:31
MEDIDO: `models/anims` é a maior pasta de modelo do jogo (570 arquivos, 35,6 MB) e era a única
sem otimizador nenhum. Não há malha skinned aqui — 27,5 dos 35,6 MB são keyframe puro — então o
veto do optimize-tribos a quantizar malha não se aplica; o que se aplica é MEDIR o desvio.

Pré-requisito que quase passou batido: o jogo usava `new GLTFLoader()` SEM decodificador. GLB
com EXT_meshopt_compression simplesmente não abriria — e o catch por arquivo faria isso sumir
calado em boneco procedural. O decoder (28 KB) vai vendorizado e registrado nos três
carregadores (glbchars, weapons, fparms), e o tools/merge-anims.mjs também precisou dele.

DESVIO MEDIDO, com a unidade certa: o rig do Mixamo é autorado em CENTÍMETROS e o `Armature`
tem escala 0,01 — medir o canal cru e chamar de mm infla por 100 (minha 1ª leitura deu "42 mm"
quando o desvio real é 0,42 mm). Régua AD converte pela escala do Armature:
  translação 0,420 mm (teto 2) · rotação 0,829° (teto 1,5)

Conferido rodando: o boneco continua animando no navegador (osso deslocou 2,9 m em 1,5 s),
anims:check e anims:merge:check verdes, check:fast 58/58. Mutante `teto-frouxo` acende as duas
cláusulas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
Agent: Claude Code
Rodado `npm run anims:otimiza`. 35,6 -> 13,9 MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Commit-grande: 570 GLBs de clipe reescritos pelo optimize-anims — binário, sem como dividir
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
Agent: Claude Code
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
Agent: Claude Code
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
Agent: Claude Code
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
csbrasil Ready Ready Preview Aug 22, 2026 12:56am

Request Review

@csbrasil-BOT csbrasil-BOT added needs-staging Mudança precisa validação integrada em staging target:main PR apontando para a branch main labels Aug 21, 2026
@csbrasil-BOT

csbrasil-BOT commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

csbrasil-bot classification

Note

@rubenmarcus abriu 590 arquivo(s) · +43 −32, base main.

O que mudou

área arquivos + amostra
🎨 assets 85 +0 −0 adjim.glb, crouch.glb, crouchwalk.glb
📚 docs 11 +30 −30 ARCH.generated.md, README.md, STATUS.md
🎮 jogo 3 +9 −0 fparms.js, glbchars.js, weapons.js
📦 outros 1 +4 −2 package.json

Labels

label por quê
needs-staging mudança de runtime/UI — valida integrado no staging antes do merge
target:main vai direto pra main, sem fila de integração

@estraga-codigo estraga-codigo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Palhaço Mal

Palhaço Mal · revisor da casa · 🤡

mapa da mudança

  • assets (570): adjim.glb, crouch.glb, crouchwalk.glb, death.glb
  • docs (11): ARCH.generated.md, README.md, STATUS.md, arquitetura.md
  • arnês (4): ARCH.md, anims-desvio-check.mjs, merge-anims.mjs, optimize-anims.mjs
  • jogo (3): fparms.js, glbchars.js, weapons.js
  • outros (2): package.json, meshopt_decoder.module.js

Tip

Risco BAIXO · nota 10/10 ▓▓▓▓▓▓▓▓▓▓

limpo pelos critérios das minhas ações desta rodada

Revisão de bot — fio não resolvido TRAVA o merge (a main exige conversa resolvida). Resolve ou refuta.

impacto cruzado

graph RL
  game_js["game.js"] --> fparms_js["fparms.js"]
  main_js["main.js"] --> fparms_js["fparms.js"]
  fparms_js["fparms.js"] --> glbchars_js["glbchars.js"]
  game_js["game.js"] --> glbchars_js["glbchars.js"]
  loading3d_js["loading3d.js"] --> glbchars_js["glbchars.js"]
  fparms_js["fparms.js"] --> weapons_js["weapons.js"]
  game_js["game.js"] --> weapons_js["weapons.js"]
  glbchars_js["glbchars.js"] --> weapons_js["weapons.js"]
Loading

(grafo local de public/js, uma rodada atrás do head do PR.)

maquiagem de verde por cima de vermelho eu desenmascaro no primeiro golpe de vista 🤡

Dos 13 arquivos em conflito, 12 eram gerados e vieram da main, re-derivados
depois. O único de verdade era o package.json, resolvido por união: `eval:mutcega`
que a main ganhou no #416 mais `eval:animsdesvio` deste PR.

E o #416, que acabou de entrar, mordeu este PR na porta: o `anims-desvio-check.mjs`
nasceu com o mesmo defeito das outras cinco réguas - anunciava "portão cego" e
saía por `falhas.length`, que ali é zero por definição, então a prova de que a
régua morde sairia VERDE justo quando ela não mordesse. Aplicado o padrão da casa:
o caso entra na lista de falhas.

Nenhum arquivo de animação, de código ou de asset foi tocado.

Agent: Claude Opus 5 (Claude Code)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>

@estraga-codigo estraga-codigo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Palhaço Mal

Palhaço Mal · revisor da casa · 🤡

mapa da mudança

  • assets (570): adjim.glb, crouch.glb, crouchwalk.glb, death.glb
  • docs (11): ARCH.generated.md, README.md, STATUS.md, arquitetura.md
  • arnês (4): ARCH.md, anims-desvio-check.mjs, merge-anims.mjs, optimize-anims.mjs
  • jogo (3): fparms.js, glbchars.js, weapons.js
  • outros (2): package.json, meshopt_decoder.module.js

Tip

Risco BAIXO · nota 10/10 ▓▓▓▓▓▓▓▓▓▓

nada aqui que quebre amanhã. amanhã é outro diff

Revisão de bot — fio não resolvido TRAVA o merge (a main exige conversa resolvida). Resolve ou refuta.

impacto cruzado

graph RL
  game_js["game.js"] --> fparms_js["fparms.js"]
  main_js["main.js"] --> fparms_js["fparms.js"]
  fparms_js["fparms.js"] --> glbchars_js["glbchars.js"]
  game_js["game.js"] --> glbchars_js["glbchars.js"]
  loading3d_js["loading3d.js"] --> glbchars_js["glbchars.js"]
  fparms_js["fparms.js"] --> weapons_js["weapons.js"]
  game_js["game.js"] --> weapons_js["weapons.js"]
  glbchars_js["glbchars.js"] --> weapons_js["weapons.js"]
Loading

(grafo local de public/js, uma rodada atrás do head do PR.)

maquiagem de verde por cima de vermelho eu desenmascaro no primeiro golpe de vista 🤡

@estraga-codigo

Copy link
Copy Markdown
Collaborator

Esbirro

Esbirro · plantão da produção · 🕵️

check vermelho parado neste PR

Caution

Não sei classificar este vermelho. Segue o que o CI cuspiu — palpite de bot sobre bug alheio só atrapalha.

o quê valor
head 1f1cb7858a8f
checks vermelhos 1
classe outra
hora do plantão 2026-08-22T01:05:11.373Z

Vermelho:

build (https://github.com/corosolto/client/actions/runs/32541880514/job/96953323396)

Rabo do log:

2026-08-22T00:58:59.1492388Z                 probe de 2026-08-11T22:51:15.580Z (10.1 dias), commit 797a478 | atualize com `npm run eval:cena`
2026-08-22T00:58:59.1492996Z ·· PULADO PX1   no ADS o jogador vê a arma E a mira
2026-08-22T00:58:59.1493453Z                 exige browser — sem arnês dedicado (divida PX)
2026-08-22T00:58:59.1493997Z ·· PULADO PX2   silhuetas das 26 armas diferem (IoU par a par < 0,85)
2026-08-22T00:58:59.1494478Z                 exige browser — sem arnês dedicado (divida PX)
2026-08-22T00:58:59.1494979Z ·· PULADO PX3   mão travada no grip em todo frame de toda animação
2026-08-22T00:58:59.1495643Z                 exige browser/traço — sem arnês dedicado (divida PX)
2026-08-22T00:58:59.1496187Z ·· PULADO PX4   aliado × inimigo distinguíveis em 1 frame a 5/20/40 m
2026-08-22T00:58:59.1496661Z                 exige browser — sem arnês dedicado (divida PX)
2026-08-22T00:58:59.1497012Z
2026-08-22T00:58:59.1497209Z --------------------------------------------------------
2026-08-22T00:58:59.1497707Z CRÍTICAS: 37/52 passam  ← TPM1 VERMELHAS (novas — reprovam)
2026-08-22T00:58:59.1498579Z DÍVIDAS:  VM1, VM3, VM9, VM12, VM20, VM16, VM18, VM19, BOT8, CHR1, CHR3, CTF1, MAT2, TEX1 (KNOWN-RED.json — não reprovam, mas continuam devidas)
2026-08-22T00:58:59.1499504Z QUITADAS: CHR4 passaram — REMOVA do KNOWN-RED.json
2026-08-22T00:58:59.1499869Z AVISOS:   VM15, BOT2 fora do alvo
2026-08-22T00:58:59.1500266Z PULADAS:  5 (exigem browser ou arnês ausente)
2026-08-22T00:58:59.1500653Z --------------------------------------------------------
2026-08-22T00:58:59.1500882Z
2026-08-22T00:58:59.1511689Z ##[error]Process completed with exit code 1.


nada fora do normal no meu turno 🕵️

rubenmarcus added a commit that referenced this pull request Aug 22, 2026
Dos 10 arquivos em conflito, 9 eram gerados e vieram da main, re-derivados
depois. O package.json não trazia régua nova deste PR, então ficou o da main.

O #416 mordeu na porta, como no #413: o `vram-check.mjs` nasceu com o mesmo
defeito - anunciava "portão cego" e saía por `falhas.length`, que ali é zero por
definição. Aplicado o padrão da casa: o caso entra na lista de falhas.

Nenhum arquivo de textura ou de código foi tocado.

Agent: Claude Opus 5 (Claude Code)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
@rubenmarcus

Copy link
Copy Markdown
Member Author

O dono viu deslizamento em partida — e a régua aprovou por pouco

"jogando ficou esquisito, os personagens não andam mais, ficam deslizando"

Conferi a fiação primeiro, e ela está certa: o decodificador está vendorizado e commitado (public/vendor/addons/libs/meshopt_decoder.module.js), o import map resolve three/addons/ para ./vendor/addons/, e glbchars.js, weapons.js e fparms.js chamam setMeshoptDecoder. O clipe abre. Não é o caso de Missing required extension.

O problema é o teto da AD2. Medido nesta branch:

40989 keyframes contra o git · translação 0.000 mm (teto 2) · rotação 1.192° (teto 1.5)
✓ AD compressão de clipe dentro do desvio tolerado

1,192° consome 79% do teto de 1,5°. Um grau e dois décimos por keyframe, em cada osso, acumulado ao longo da cadeia quadril → coxa → joelho → pé, é exatamente o que produz deslizamento: o pé deixa de casar com o chão. E o jogo depende disso — glbchars.js tem footOffset() lendo foot-offsets.json, calibrado sobre os clipes originais.

A régua não está errada em existir; o limiar é que foi escolhido frouxo demais. Ela mede a coisa certa e passou num desvio que o olho reprova em partida — que é a definição de régua que não morde.

O que eu faria

Duas saídas, e a escolha é do dono:

  1. Reverter a compressão dos clipes, como ele pediu. Andar normal vale mais que 21,7 MB.
  2. Recomprimir com o teto apertado — baixar a AD2 para algo como 0,2°, rodar o encoder num nível menos agressivo e aceitar o ganho que sobrar. Parte dos 21,7 MB provavelmente sobrevive.

Em qualquer um dos dois, o teto de 1,5° não pode ficar: ele já provou que aprova o que quebra.

(Os conflitos deste PR eu resolvi antes de investigar — 12 de 13 eram arquivo gerado.)

🤖 Generated with Claude Code

@rubenmarcus

Copy link
Copy Markdown
Member Author

Fechando. O dono jogou e reprovou, e a falha é minha — em dois lugares.

1. O teto da AD2 não tinha procedência. Escolhi 1,5° porque parecia pouco. Isso é exatamente o defeito que eu apontei no #365 há algumas horas ("teto sem procedência é opinião") e cometi no meu próprio PR. Medido agora: 1,193°, ou 79% do teto — a régua aprovou o que o olho reprova em partida, que é a definição de régua que não morde.

2. A amostra escondeu o número. A AD media 3 arquivos, e o cabeçalho dela dizia que "o desvio é propriedade do encoder, não de um arquivo em particular". Essa premissa está errada: varrendo 80 arquivos, o pior é run.glb, osso LeftUpLeg — perna, no ciclo de corrida. É precisamente a cadeia quadril→coxa→joelho→pé que produz o deslizamento que o dono viu. Com 3 arquivos eu media 0,829° e dormia tranquilo.

Some-se a isso que o foot-offsets.json foi calibrado sobre os clipes originais (glbchars.js lê ele em footOffset()), então o contato de pé estava sendo corrigido com número de outro conjunto de clipes.

O ganho era real (35,6 → 13,9 MB, -61%), mas não ao custo de o boneco deslizar. Sem compressão, como o dono pediu.

Se um dia isso voltar, a régua certa não é desvio de keyframe — é a sugestão da sessão game4-fc: medir contato de pé direto, altura do pé de apoio no clipe comprimido contra o original no mesmo instante. Desvio angular é grandeza vizinha; contato de pé é o defeito. E o teto tem de sair de medição, não de impressão.

Obrigado à game4-fc pelo diagnóstico com número.

rubenmarcus added a commit that referenced this pull request Aug 22, 2026
Nasce de duas reprovações minhas. O #413 media desvio angular quando o que quebrou era contato de
pé; o #417 media VRAM quando o que quebrou era tempo de tela. VRAM e bytes são baratos de medir e
por isso sedutores; o que o jogador sente é o relógio.

TETO COM PROCEDÊNCIA — a lição que faltou nas duas: a medição varia sozinha ±8% (4 corridas na
main: 6083, 6607, 6854, 7086 ms). O teto de 9300 ms é ~1,4× a mediana, cinco vezes o ruído
observado e bem abaixo dos 12557 ms que o KTX2 no elenco produziu. Passa ruído, morde regressão.

Mutante `lento` estrangula a CPU em 4× e acende a cláusula (10.449 ms).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: rubenmarcus <rubenmarcus.dev@gmail.com>
Agent: Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-staging Mudança precisa validação integrada em staging target:main PR apontando para a branch main

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants