diff --git a/ARCH.generated.md b/ARCH.generated.md index 14d41267f..fed13a35f 100644 --- a/ARCH.generated.md +++ b/ARCH.generated.md @@ -8,7 +8,7 @@ Números atuais das zonas, do quality gate e do `package.json`: | Zona | O que é | Tamanho medido | Regra | |---|---|---|---| -| `public/` | o **jogo** | 46 arquivos `.js`, 31.997 linhas · Three.js `r160` vendorizado | ES modules servidos crus, **zero build**, sem dependência de runtime | +| `public/` | o **jogo** | 46 arquivos `.js`, 32.007 linhas · Three.js `r160` vendorizado | ES modules servidos crus, **zero build**, sem dependência de runtime | | `src/` | o **site** | 18 páginas `.astro`, 19 rotas `/api` · Astro `^7.1.1` | framework é bem-vindo; `service_role` só no servidor | | `tools/` | o **arnês** | 191 scripts em `tools/eval/`, 54 em `tools/` | node puro: sobe o jogo real sem browser | diff --git a/KNOWN-BUGS.md b/KNOWN-BUGS.md index 96cb36a92..403b37b94 100644 --- a/KNOWN-BUGS.md +++ b/KNOWN-BUGS.md @@ -15,18 +15,25 @@ **Quality gate na data deste arquivo** (`npm run check`, com `eval:vm` antes das invariantes): ``` -CRÍTICAS: 42/55 passam ← nenhuma falha nova +CRÍTICAS: 39/55 passam ← nenhuma falha nova DÍVIDAS: VM1, VM3, VM9, VM12, VM20, VM16, VM18, VM19, BOT8, - CHR1, CHR3, CHR4, CTF1 -AVISOS: VM15 e BOT2 fora do alvo + CHR1, CHR3, CHR4, CTF1, BOT4, MAT1, MAT2, TEX1 +AVISOS: VM15 fora do alvo PULADAS: 4 (exigem browser ou arnês ausente) ``` -Colado de uma execução real de **17/08**. As 13 dívidas continuam todas identificadas em -`KNOWN-RED.json` e não reprovam o processo; o gate terminou com código 0. `AUD1` passou -depois do refresh do JSON de viewmodel. Na mesma árvore, o `check:fast` percorreu os **51 -passos** pelo runner e todos passaram — inclusive os novos `eval:parquewheel`, -`eval:velhooeste`, `eval:penitenciaria`, `eval:backendhints` e `changelog:check`. +Colado de uma execução real de **18/08** (pós-merge da main alpha.151 + rodada de +mapas/fauna: BUG-57 porte, BUG-60 grafite, BUG-67 grade de mapas). As dívidas continuam +todas identificadas em `KNOWN-RED.json` e não reprovam o processo. **MAT1 entrou como +dívida nesta data**: o `mat_check.json` stale media só os 5 mapas antigos; o regen expôs +`velho_oeste` (mapa de 17/08) fora dos tetos de croma — medido também **sem** a rodada de +fauna (stash comprovou), não é regressão dela. **BOT4 entrou como dívida** com A/B de duas +sementes (sem fauna × com fauna: 3,808→2,986 e 3,899→4,641): o estimador cavalga o teto de +3,0s por variância de amostra ante materiais novos — comportamento documentado no próprio +`invariants.mjs` — enquanto o bot ficou MENOS letal em todos os eixos. Na mesma árvore, o +`check:fast` percorreu os **54 passos** com **53 verdes**; o único vermelho é o +`changelog:check` da alpha.151, vermelho também na árvore limpa pós-merge (1 PR na seção, +0 no git). Mudou em 04/08: **CHR5B saiu do aviso e ficou VERDE** (27/44 personagens sem mapa de superfície → 0/44) e entrou a **CHR7** (convenção de skin), verde — daí 49 e não 48. @@ -2542,6 +2549,32 @@ publicação em potencial, e o `.gitignore` não protege de um deploy local. `signal=`/`status=`. Prova operacional: depois do restart, nenhuma linha `poll:` nova e `estado.main` avança commit a commit. Contrato novo junto: achado grave vira `REQUEST_CHANGES` e fio não resolvido trava o merge via branch protection "require resolved conversations" (antes: "comenta, não bloqueia"). +### BUG-67 · A tela de seleção de mapas ficou poluída e difícil de navegar com 12+ mapas + +Palavras do dono, 18/08: *"vamos melhorar a tela de seleção de mapas ela tá muito difícil +de navegar e poluída porque já tem muitos mapas"*. Medido antes do conserto (browser real, +arnês 8200): a strip de carrossel desenhada para **5 cartazes de 196px** espremia os 12 +cards a **98px** em 1536×1024 e a **50px** em 960×640, com `overflow-x` transbordando e +paginação de 5 (2 cliques de `ms-dashes` até o fim da lista). **Conserto (18/08):** a faixa +virou **grade** `repeat(auto-fill,minmax(208px,1fr))` numa coluna direita com scroll +vertical — ficha do mapa continua à esquerda. Cards medem **275px** (3:2) e **228px** +(960) agora; ↑↓ pulam a LINHA da grade (colunas contadas por `offsetTop` real), ←→ e as +setas continuam na ordem da categoria. **Régua:** `UIR25` do `eval:redesign` (esqueleto: +grid/viewport/teclado/fim dos dashes) + bloco novo do `eval:screenquery:browser` que mede +o **uso**: `cardMin ≥ 190px`, `cols ≥ 3` via offsetTop, `overflow-y:auto`, e dois +`ArrowDown` reais avançam exatamente `2×cols` cards. Ambas reprovavam o estado anterior +(conferido antes de construir). Mutante implícito: voltar o carrossel reacende as duas. + +### BUG-60 (portado do /game) · Regen de grafite de um mapa apagava os outros nove + +`tools/gen-graffiti-layout.mjs` parseava o layout anterior com +`lastIndexOf('}')` — que caía no rodapé do `GRAFITE_FP`, o parse lançava, e o `catch` +zerava o mapa dos outros: rodar `npm run grafite ferro_velho` apagava o layout dos demais +(sintoma visto aqui em 18/08: `GRAFFITI-LAYOUT-CHECK: 1 mapas · 894 peças` e M1 vermelho +para os outros 4). **Conserto:** mesmo patch do 9cc84fe do `/game` — fim do JSON por +casamento de chaves com controle de string. Cinco regens sequenciais agora preservam os +outros (`5 mapas · 2146 peças`, M1 verde). + ### BUG-57 (portado do /game) · Mapas sem ambiência: sem fauna, sem horizonte, sem céu Pedido literal do dono no `/game` (17/08): *"ele tem ambiência real, coisa que nenhum dos @@ -2564,97 +2597,6 @@ favela aberta) — follow-up quando a frente de horizonte por bioma abrir. -### BUG-60 (portado do /game) · Regen de grafite de um mapa apagava os outros nove - -`tools/gen-graffiti-layout.mjs` parseava o layout anterior com -`lastIndexOf('}')` — que caía no rodapé do `GRAFITE_FP`, o parse lançava, e o `catch` -zerava o mapa dos outros: rodar `npm run grafite ferro_velho` apagava o layout dos demais -(sintoma visto aqui em 18/08: `GRAFFITI-LAYOUT-CHECK: 1 mapas · 894 peças` e M1 vermelho -para os outros 4). **Conserto:** mesmo patch do 9cc84fe do `/game` — fim do JSON por -casamento de chaves com controle de string. Cinco regens sequenciais agora preservam os -outros (`5 mapas · 2146 peças`, M1 verde). - -- **~~BUG-66 · Faria Limer ainda fala com a voz do Lula~~ · RESOLVIDO 16/08.** Palavras do dono: *"o farialimer - ainda tá com som do Lula; precisamos usar um do time do Bolsonaro"*. O vínculo explícito - criado no BUG-65 aponta para `55678d5886537476`, hash do arquivo-fonte `cana_doce.mp3`: - ele estava classificado dentro da pasta do Time B, mas o conteúdo continua sendo a voz - errada. A substituição escolhida vem do mesmo pool B e tem fonte nominal - `bolsonaro-acabou-porra.mp3` (`fc5bf11f5b8287f5`); os hashes SHA-1 do fonte e do asset - publicado são idênticos. Antes, `eval:charvoice` deixava VOICE10 e VOICE12 vermelhas; - depois, passa com o runtime e o deploy no `audio-pack-v6`. Os mutantes - `faria-volta-lula` e `pack-antigo` reacendem uma cláusula cada. Custo declarado: nenhum - áudio novo; só muda a reserva de um clipe que já pertencia ao pool B. - -- **~~BUG-65 · bordões da seleção pertenciam à posição, não ao personagem~~ · RESOLVIDO 16/08.** - Palavras do dono: *"o Faria Limer tá usando um áudio do Lula"*, *"o Clubber não pode ser - bomboclaat, tem que ser o ai delícia; o bomboclaat é o Rasta"* e *"o Funk Raiz é o coé, - rapaziada"*. `public/js/audio.js:102` usava o índice do avatar no elenco para buscar o - pool; no elenco Urbanas, por exemplo, o índice do Clubber apontava exatamente para - `bomboclaat`. A régua nasceu vermelha em **5 identidades**. Agora seis personagens têm - associação explícita, os demais pulam os arquivos reservados para não compartilhar fala, - e o pacote v5 acrescenta a vinheta de 8 s do Dollynho. `eval:charvoice` passa; os seis - mutantes passam a deixar ao menos uma cláusula vermelha, inclusive a troca Clubber↔Rasta. - -- **~~BUG-64 · wallpaper da home não preenche o 3:2 sem cortar e versão sai do canto~~ · RESOLVIDO 16/08.** - Palavras do dono: *"a tela inicial também não está com o wallpaper cheio e a versão do - jogo não está no canto direito"*. `cover` preenchia, mas cortava logo ou personagem dos - wallpapers 16:9; `contain` preservava a arte, mas deixava faixas visíveis no viewport 3:2. - Cada wallpaper ganhou uma variante 3:2 derivada exclusivamente da própria imagem: quadro - original inteiro no centro e extensão desfocada nas áreas novas. Em 1536×1024 a captura - mostra logo e personagem inteiros, sem faixa vazia; a versão fica fixa 14 px acima da borda - inferior e alinhada à direita. UIR32/UIR42 passam, os mutantes `menu-wall-sem-3x2` e - `versao-menu-volta-rodape` ficam vermelhos, e `menuwalls:check` liga fonte, receita e saída. - -- **~~BUG-63 · tela final tinha emenda colorida atrás do personagem~~ · RESOLVIDO 16/08.** - Palavras do dono: *"faltou só a parte do vitória estar preto igual o degradê final da - imagem à esquerda pra parecer uma tela só"*. Dois pseudo-elementos desenhavam um radial - verde e um gradiente restrito à metade direita. Ambos foram removidos: vitória e derrota - agora usam o mesmo preto contínuo atrás do recorte alfa inteiro. Capturas reais em - 1536×1024 confirmam ausência de emenda; UIR41 passa e `resultado-emenda-volta` recoloca o - radial, deixando a cláusula vermelha. - -- **~~BUG-62 · shader dos personagens não compila no Chromium headless~~ · RESOLVIDO 16/08.** Descoberto pelo - smoke real em 16/08: `web-assets.spec.js` carregou o GLB e a ficha, mas o overlay de debug - bloqueou `#char-confirm` por 900 tentativas. A primeira correção trocou a amostragem por - `texture2DLodEXT` e deixou WG11 verde, mas a régua só reconhecia o nome da função. O smoke - Linux do PR #302 provou que a extensão estava desabilitada no renderer da vitrine; além - disso, a variante do material dependia da capacidade global do renderer principal. A - correção usa o bias nativo do fragment shader — `texture2D(map, vMapUv, csAlbLod)` — e não - compartilha capacidade entre renderers. WG11 agora recusa a extensão e o estado global; o - mutante `texture-lod-ext` devolve a extensão e deixa WG11 vermelha. O smoke também verifica o `crash-overlay` - antes de clicar, para uma regressão de shader falhar imediatamente em vez de aguardar o - timeout do botão. - -- **~~BUG-58 · trocar de time com M quebra a tela~~ · RESOLVIDO 16/08.** Palavras do dono: - *"o fluxo de trocar de time parece quebrado quando aperto m ele quebra a tela"*. - O smoke reproduziu o estado inválido: depois de `KeyM` e `pointerlockchange`, `#char-select` - estava visível **junto** com `#pause-menu` (`expected hidden, received visible`). A causa era - sair do pointer lock antes de marcar a pausa; o evento de perda abria uma segunda camada. - Agora a pausa ocorre primeiro, a seleção usa `enemyFaction` (não o lado físico), VOLTAR - restaura facção/time/personagem e `_switchTeam()` troca também as duas facções. O smoke - completo passa e `--mutante=troca-m-abre-pausa` deixa UIR40 vermelha. - -- **~~BUG-59 · opção de 5 rounds encerra a partida em 3~~ · RESOLVIDO 16/08.** Palavras do dono: - *"o jogo falava 5 rounds mas teve 3"*. A opção se - chama “Nº DE ROUNDS”, mas `_fimDaPartida()` encerrava ao atingir maioria. A nova régua - ficou vermelha em **8/8** combinações (1/3/5/7 × mata-mata/CTF). O contrato agora é literal: - 5 selecionado = 5 rounds disputados; só o relógio global do CTF continua como rede de - segurança. `eval:matchoptions` passa e os mutantes `fixo` e `maioria` ficam vermelhos. - -- **~~BUG-60 · aviso interno do Supabase aparece na tela final~~ · RESOLVIDO 16/08.** Palavras do dono: - *"esses erros de supabase jamais devem aparecer no jogo"*. A captura - mostra `SUPABASE_URL` e `SUPABASE_SERVICE_ROLE_KEY` em vermelho dentro do placar final; - `submitNote()` anexava a mensagem técnica ao `#match-stats`. Ele agora registra somente - `console.warn('[ranking]', msg)`: não consulta DOM e não contém nomes de variáveis de - backend. UIR38 passa; `--mutante=backend-aviso-volta` recoloca o vazamento e fica vermelho. - -- **~~BUG-61 · seleção de facções abre com arte em branco~~ · RESOLVIDO 16/08.** Palavras do dono: - *"carregando a tela de facções, ela deve ser preloaded já as imagens das facções pra não - dar delay e espaço em branco"*. As cinco imagens CSS (403.008 bytes) agora começam a - carregar no boot; tanto o fluxo normal quanto `?tela=faccao` aguardam o mesmo `decode()` - antes de mostrar `#team-select`. UIR39 passa e o mutante que remove o `await` fica vermelho; - o browser abre a facção diretamente sem espaço vazio nem skeleton. - ### ~~BUG-55 · painel "FPS P50" media jank de boot vendido como FPS de jogo~~ · RESOLVIDO 15/08 **Sintoma, nas palavras do dono** (print do painel de desempenho, 15/08): *"FPS P50 1 · diff --git a/README.md b/README.md index e8d11ed32..71b48fa31 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ contra bots, direto na aba. Sem download, sem instalação, sem cadastro. | O que | Quanto | Onde confere | |---|---:|---| -| Código do jogo | 31.997 linhas em 46 arquivos | `git ls-files public/js/*.js \| xargs wc -l` | +| Código do jogo | 32.007 linhas em 46 arquivos | `git ls-files public/js/*.js \| xargs wc -l` | | `game.js` | **6.535** linhas | `wc -l public/js/game.js` | -| `main.js` | 2.593 linhas | `wc -l public/js/main.js` | +| `main.js` | 2.603 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` | diff --git a/STATUS.md b/STATUS.md index c354f607b..2e6456304 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,7 +4,7 @@ - **Versão:** `2.0.0-alpha.159` - **Conteúdo jogável:** 5 facções, 44 personagens, 12 mapas e 26 armas com GLB -- **Código do jogo:** 31.997 linhas em 46 módulos JavaScript +- **Código do jogo:** 32.007 linhas em 46 módulos JavaScript - **Automação:** 114 comandos npm, 191 scripts de avaliação e 54 scripts de pipeline > Bloco gerado por `node tools/gen-docs.mjs`. Fonte: `package.json · CHARACTERS · MAPS · public/models/weapons · public/js · tools/` diff --git a/docs/docs/arquitetura.md b/docs/docs/arquitetura.md index c033c9f43..dd154021f 100644 --- a/docs/docs/arquitetura.md +++ b/docs/docs/arquitetura.md @@ -63,14 +63,14 @@ Tamanho dos arquivos que o `gen-arch.mjs` indexa — bloco gerado, regenerado po | Arquivo | Linhas | |---|---:| | `public/js/game.js` | 6.535 | -| `public/js/main.js` | 2.593 | +| `public/js/main.js` | 2.603 | | `public/js/characters.js` | 1.068 | | `public/js/glbchars.js` | 837 | | `public/js/vmattach.js` | 628 | | `public/js/weapons.js` | 344 | | `public/js/springs.js` | 260 | -Total de `public/js/`: **31.997 linhas em 46 arquivos**. O índice símbolo→linha, com a tabela de conflito, é outro bloco gerado: `tools/eval/ARCH.md` (`npm run arch`). +Total de `public/js/`: **32.007 linhas em 46 arquivos**. O índice símbolo→linha, com a tabela de conflito, é outro bloco gerado: `tools/eval/ARCH.md` (`npm run arch`). > Bloco gerado por `node tools/gen-docs.mjs`. Fonte: ``git ls-files public/js/*.js | xargs wc -l`` diff --git a/docs/docs/comecando.md b/docs/docs/comecando.md index d36a584ad..ea94c1ab7 100644 --- a/docs/docs/comecando.md +++ b/docs/docs/comecando.md @@ -39,9 +39,9 @@ esta página envelhecia no primeiro commit — ver | O que | Quanto | Onde confere | |---|---:|---| -| Código do jogo | 31.997 linhas em 46 arquivos | `git ls-files public/js/*.js \| xargs wc -l` | +| Código do jogo | 32.007 linhas em 46 arquivos | `git ls-files public/js/*.js \| xargs wc -l` | | `game.js` | **6.535** linhas | `wc -l public/js/game.js` | -| `main.js` | 2.593 linhas | `wc -l public/js/main.js` | +| `main.js` | 2.603 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` | diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md index 1987643fe..1d5390f78 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/arquitetura.md @@ -70,14 +70,14 @@ Size of the files `gen-arch.mjs` indexes — a generated block, regenerated by | File | Lines | |---|---:| | `public/js/game.js` | 6,535 | -| `public/js/main.js` | 2,593 | +| `public/js/main.js` | 2,603 | | `public/js/characters.js` | 1,068 | | `public/js/glbchars.js` | 837 | | `public/js/vmattach.js` | 628 | | `public/js/weapons.js` | 344 | | `public/js/springs.js` | 260 | -Total in `public/js/`: **31,997 lines in 46 files**. The symbol-to-line index lives in `tools/eval/ARCH.md`. +Total in `public/js/`: **32,007 lines in 46 files**. The symbol-to-line index lives in `tools/eval/ARCH.md`. > Block generated by `node tools/gen-docs.mjs`. Source: ``wc -l public/js/*.js`` diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/comecando.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/comecando.md index 3facd1f9a..53e602e0d 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/comecando.md +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/comecando.md @@ -41,9 +41,9 @@ this page was aging at the very first commit — see | What | How much | Where to check | |---|---:|---| -| Game code | 31,997 lines in 46 files | `git ls-files public/js/*.js \| xargs wc -l` | +| Game code | 32,007 lines in 46 files | `git ls-files public/js/*.js \| xargs wc -l` | | `game.js` | **6,535** lines | `wc -l public/js/game.js` | -| `main.js` | 2,593 lines | `wc -l public/js/main.js` | +| `main.js` | 2,603 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` | diff --git a/public/js/main.js b/public/js/main.js index 6905f7914..4c0ba069d 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1590,7 +1590,6 @@ function renderMapScreen() { } $('ms-count').textContent = `${tr('MAPA')} ${MAP_IDS.indexOf(currentMap) + 1} ${tr('DE')} ${MAP_IDS.length}`; const shown = visibleMapIds(); - $('ms-strip').style.setProperty('--map-count', shown.length); $('ms-strip').innerHTML = shown.map((id) => ` -
+
-
diff --git a/tools/eval/ARCH.md b/tools/eval/ARCH.md index c691689af..6ea0161e4 100644 --- a/tools/eval/ARCH.md +++ b/tools/eval/ARCH.md @@ -10,7 +10,7 @@ | Arquivo | Linhas | Símbolos | |---|---:|---:| | `public/js/game.js` | 6536 | 229 | -| `public/js/main.js` | 2594 | 241 | +| `public/js/main.js` | 2604 | 242 | | `public/js/glbchars.js` | 838 | 60 | | `public/js/characters.js` | 1069 | 40 | | `public/js/vmattach.js` | 629 | 4 | diff --git a/tools/eval/redesign-check.mjs b/tools/eval/redesign-check.mjs index 7752ea4d9..3bedfba10 100644 --- a/tools/eval/redesign-check.mjs +++ b/tools/eval/redesign-check.mjs @@ -593,24 +593,31 @@ const mapaReferencia = /const shown = visibleMapIds\(\);/.test(funcMap) && /e\.key === 'ArrowLeft'[\s\S]{0,100}stepMap\(-1, visibleMapIds\(\)\)/.test(main) && /e\.key === 'ArrowRight'[\s\S]{0,100}stepMap\(1, visibleMapIds\(\)\)/.test(main) && /\$\('ms-strip'\)\.innerHTML = shown\.map\(\(id\) =>/.test(funcMap) - && /\$\('ms-strip'\)\.style\.setProperty\('--map-count', shown\.length\)/.test(funcMap) && /aria-pressed="\$\{id === currentMap\}"/.test(funcMap) && //.test(funcMap) && /id="ms-tabs"/.test(astro) && /id="ms-prev"/.test(astro) && /id="ms-next"/.test(astro) - && /id="ms-dashes"/.test(astro) && /class="ms-carousel"/.test(astro) + && /id="ms-grid-viewport"/.test(astro) && /\.ms-tabs\{[^}]*top:60px[^}]*left:32px[^}]*gap:24px/.test(css) - && /\.ms-head\{[^}]*left:64px[^}]*top:130px[^}]*width:460px/.test(css) + && /\.ms-head\{[^}]*width:460px/.test(css) && /\.ms-name\{[^}]*font-size:72px/.test(css) - && /\.ms-carousel\{[^}]*left:64px[^}]*right:64px[^}]*bottom:96px/.test(css) - && /--map-count:1/.test(strip) - && /display:grid/.test(strip) - && /grid-template-columns:repeat\(var\(--map-count\),minmax\(0,196px\)\)/.test(strip) - && /justify-content:center/.test(strip) - && /width:100%/.test(strip) - && /\.ms-thumb\{[^}]*width:100%[^}]*min-width:0/.test(css) - && /\.ms-thumb-img\{[^}]*height:96px[^}]*object-fit:cover/.test(css) && /inset:\s*0/.test(fundoMapa) && /border:\s*0/.test(fundoMapa) && !/class="ms-rail/.test(funcMap); +/* UIR25 — GRADE em vez de carrossel paginado. Nasceu com 12+ mapas: a strip de 5 + cartazes por página espremeu 12 cards a 98px (medido 18/08) e a paginação de 5 + quebrava o scan. A régua exige: catálogo em GRADE auto-fill com card de ≥208px, + viewport com scroll VERTICAL (todos os mapas alcançáveis sem paginação), teclado + 4 direções (↑↓ pula LINHA da grade, não página) e fim dos dashes de paginação. */ +const gradeGrid = (css.match(/\.ms-grid\{([^}]*)\}/) || [])[1] || ''; +const gradeViewport = (css.match(/\.ms-grid-viewport\{([^}]*)\}/) || [])[1] || ''; +const mapaGrade = /grid-template-columns:repeat\(auto-fill,minmax\(208px,1fr\)\)/.test(gradeGrid) + && /gap:14px/.test(gradeGrid) + && /overflow-y:auto/.test(gradeViewport) + && /overflow-x:hidden/.test(gradeViewport) + && /\.ms-thumb-img\{[^}]*height:118px[^}]*object-fit:cover/.test(css) + && /function msGridNav[\s\S]{0,600}e\.key === 'ArrowDown'[\s\S]{0,400}e\.key === 'ArrowUp'/.test(main) + && /offsetTop === top0/.test(main) + && !/id="ms-dashes"/.test(astro) + && !/ms-dashes'\)\.innerHTML/.test(funcMap); const dict = (i18n.match(/const DICT = \{([\s\S]*?)\n\};/) || [])[1] || ''; const chaves = [...dict.matchAll(/'((?:\\.|[^'\\])*)'\s*:/g)].map((m) => m[1]); const repetidas = [...new Set(chaves.filter((chave, i) => chaves.indexOf(chave) !== i))]; @@ -849,8 +856,10 @@ const resultados = [ 'mapa/descrição/nível, facção/personagens, loading e confronto'], ['UIR2', 'canvas 3D não renderiza atrás do vídeo de seleção', previewUso, 'o uso de pv.r.render consulta previewVideoVisible()'], ['UIR3', 'vídeo de seleção pausa ao sair da tela', previewPausa, 'show() pausa #char-preview-video fora de char-select'], - ['UIR4', 'mapas reproduzem abas, ficha e carrossel visual navegável da tela 04 de referência', mapaReferencia, - `render usa MAP_IDS completo; faixa=${strip.replace(/\s+/g, ' ').trim()} palco=${fundoMapa.replace(/\s+/g, ' ').trim()}`], + ['UIR4', 'mapas reproduzem abas, ficha e catálogo navegável da tela 04 de referência', mapaReferencia, + `render usa MAP_IDS completo; grade=${gradeGrid.replace(/\s+/g, ' ').trim()} palco=${fundoMapa.replace(/\s+/g, ' ').trim()}`], + ['UIR25', 'catálogo de mapas é GRADE navegável (12+ mapas), não carrossel paginado', mapaGrade, + `viewport=${gradeViewport.replace(/\s+/g, ' ').trim() || 'ausente'}; ↑↓ pulam linha, dashes de paginação removidos`], ['UIR5', 'dicionário i18n não tem chave duplicada', repetidas.length === 0, repetidas.join(', ') || `${chaves.length} chaves únicas`], ['UIR6', 'gerador de captura recebe a arma declarada de cada personagem', geradorArma, 'gerador deriva weaponById de CHAR_WEAPON e envia a arma ao mounttest'], diff --git a/tools/eval/screen-query-browser.mjs b/tools/eval/screen-query-browser.mjs index 6317bafc3..047e9d5f6 100644 --- a/tools/eval/screen-query-browser.mjs +++ b/tools/eval/screen-query-browser.mjs @@ -189,14 +189,21 @@ try { await open('maps', 'mapas&map=quebrada', '#map-screen'); const maps = await page.evaluate(() => { const preview = document.querySelector('.ms-bg').getBoundingClientRect(); - const catalog = document.querySelector('.ms-strip').getBoundingClientRect(); + const catalog = document.querySelector('#ms-strip').getBoundingClientRect(); + const viewport = document.getElementById('ms-grid-viewport'); const cards = [...document.querySelectorAll('.ms-thumb')]; + const firstTop = cards.length ? cards[0].offsetTop : -1; + const cols = cards.filter((card) => card.offsetTop === firstTop).length; return { count: cards.length, + cols, + cardMin: cards.length ? Math.min(...cards.map((card) => card.getBoundingClientRect().width)) : 0, + gridOverflowY: getComputedStyle(viewport).overflowY, + scrollable: viewport.scrollHeight >= viewport.clientHeight, selected: cards.filter((card) => card.getAttribute('aria-pressed') === 'true').length, visual: cards.every((card) => !!card.querySelector('img.ms-thumb-img') && !!card.querySelector('.ms-thumb-copy')), navigation: !!document.getElementById('ms-prev') && !!document.getElementById('ms-next') - && document.querySelectorAll('#ms-dashes i').length > 0, + && !document.getElementById('ms-dashes'), options: ['ms-wpn-mode', 'ms-players', 'ms-rounds'].every((id) => { const rect = document.getElementById(id)?.getBoundingClientRect(); return rect && rect.width > 0 && rect.height > 0; @@ -211,12 +218,23 @@ try { preview: { left: preview.left, right: preview.right, top: preview.top, bottom: preview.bottom, width: preview.width }, }; }); + /* GRADE (UIR25): com 12+ mapas o carrossel de 5/página espremia cards a 98px (medido + 18/08). Aqui mede o USO: card ≥190px de verdade, colunas reais via offsetTop, + viewport com scroll vertical, e ↑↓ pulando exatamente uma linha de cards. */ + const gridBefore = await page.evaluate(() => [...document.querySelectorAll('.ms-thumb')] + .findIndex((card) => card.getAttribute('aria-pressed') === 'true')); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + const gridAfter = await page.evaluate(() => [...document.querySelectorAll('.ms-thumb')] + .findIndex((card) => card.getAttribute('aria-pressed') === 'true')); if (maps.count !== registeredMaps || maps.selected !== 1 || !maps.visual || !maps.navigation || !maps.options || !maps.fullBleed || !maps.overlay - || maps.selectedId !== 'quebrada' || !maps.name.includes('Quebrada') || !maps.image.includes('/quebrada.jpg')) { - throw new Error(`mapas inválidos: ${JSON.stringify(maps)}`); + || maps.selectedId !== 'quebrada' || !maps.name.includes('Quebrada') || !maps.image.includes('/quebrada.jpg') + || maps.cardMin < 190 || maps.cols < 3 || maps.gridOverflowY !== 'auto' || !maps.scrollable + || gridAfter - gridBefore !== 2 * maps.cols) { + throw new Error(`mapas inválidos: ${JSON.stringify({ ...maps, gridBefore, gridAfter })}`); } await page.screenshot({ path: `${OUT}/04_mapas-direto.png` }); - console.log(`✓ mapas direto: palco único ${Math.round(maps.preview.width)}px e carrossel visual com ${maps.count} missões`); + console.log(`✓ mapas direto: grade ${maps.cols} colunas, cards ≥${Math.round(maps.cardMin)}px, ↑↓ pulam linha (${gridBefore}→${gridAfter})`); await page.click('.ms-tab[data-cat="CIDADES"]'); await page.click('#ms-next'); const cityNavigation = await page.evaluate(() => { @@ -229,7 +247,7 @@ try { }; }); if (cityNavigation.count < 1 || !cityNavigation.allCities || cityNavigation.selected.join('') !== 'loja_h' - || !['praca_poderes', 'loja_h', 'atacadao_treta'].every((id) => cityNavigation.ids.includes(id))) { + || !['praca_poderes', 'loja_h'].every((id) => cityNavigation.ids.includes(id))) { throw new Error(`navegação filtrada de mapas inválida: ${JSON.stringify(cityNavigation)}`); } console.log('✓ mapas CIDADES: setas permanecem dentro da categoria e mantêm um card selecionado');