fix: carregador do mural à prova de loop no celular

build-atlas.js gera páginas leves de 3072 px para celular (--so-mobile
regera só elas) e registra o campo mobile no atlas-map.json.

wall.html usa essas páginas em toque ou GPU/aparelho fraco, detecta pela
marca em sessionStorage que a tentativa anterior morreu e força o modo leve,
e trata falha de download, de decodificação e perda de contexto WebGL com
aviso e botão, sem recarregar sozinho. Detalhes e testes no achado 3.5.
This commit is contained in:
rodado
2026-09-14 12:37:59 +02:00
parent 10b7cb0cb9
commit fc45ccd9d0
4 changed files with 193 additions and 7 deletions
+50
View File
@@ -266,6 +266,56 @@ antes de tudo isso o mapa era 5,52 MB / 0,73 MB.
debaixo do `#card-wrap` (20). Agora 25 (scrim 24), e no celular a ficha
estaciona acima do peek da sheet para não cobrir ‹ anterior / próxima ›.
### Achado 3.5 — o mural entrava em loop de carregamento no celular
Relato do usuário (14/09): no celular a tela inicial ficou em loop. Causa, lida
no carregador: o celular **decodificava cada página em 8192×8192** (~268 MB) e
só então reduzia para 4096 num canvas, retendo os canvases além das texturas —
pico de ~800 MB. O iOS não lança erro por falta de memória: mata a aba e a
**recarrega sozinho**, e a página recomeçava o mesmo carregamento. Não havia
tratamento de erro nenhum: fetch falho ou decodificação recusada deixavam a
barra parada para sempre.
Correção (commit `xyz` e `brasiliano` de 14/09):
- **Páginas leves** `atlas-m-0..3.webp` (3072², peça de 48 px), geradas pelo
`build-atlas.js` (`--so-mobile` regera só elas) e registradas em
`atlas-map.json` no campo `mobile`. Mesma posição de cada peça, então a conta de
uv não muda. 6,8 MB no total contra 42 MB; 36 MB decodificados por página.
- **Modo leve** em toque, `maxTextureSize` < 8192 ou `deviceMemory` ≤ 4, ou
forçado com `localStorage.mural_leve = '1'`.
- **Guarda contra loop**: marca `mural_carregando` em `sessionStorage` antes da
parte pesada, apagada só depois de dois quadros renderizados. Se a página abre
com a marca, a tentativa anterior morreu → vai direto para o modo leve e
mostra "modo leve — a tentativa anterior não terminou".
- **Nunca recarrega sozinho**: HTTP de erro, decodificação recusada e
`webglcontextlost` mostram aviso com botão "tentar de novo" (que recarrega uma
vez, já no modo leve). Progresso parado por 30 s mostra "o carregamento parou
de avançar" sem derrubar o que ainda pode estar vivo.
Verificado com Chrome real via `puppeteer-core` (perfil próprio, WebGL
swiftshader), emulando celular com toque — roteiros em
`scratchpad/teste-mural/testa.mjs` e `testa2.mjs`:
| cenário | resultado |
|---|---|
| A — celular carrega | só as 4 páginas leves, capa some em 8,3 s, marca apagada |
| C — marca de crash no desktop | aviso de modo leve, nenhuma página pesada, sem recarga |
| D — página falha no download | aviso com botão, **nenhuma recarga em 10 s** |
| E — botão tentar de novo | recarrega uma vez, termina no modo leve, sem 2ª recarga |
| F — perda de contexto WebGL | aviso com botão, sem recarga |
| G — download travado | aviso "parou de avançar" em 33 s, sem recarga |
O card no modo leve recorta a peça certa: a mesma peça tirada do atlas normal e
do leve difere 4/255 em média; a vizinha, 41,5/255.
**Limite do teste**: não há iPhone de verdade aqui — o limite de memória do iOS
não se emula no Chrome. O que está provado é que o celular não decodifica mais
8192² e que nenhum caminho de falha recarrega sozinho.
**Armadilha de teste**: `framenavigated` do Puppeteer conta a `about:blank`
inicial e mudanças de histórico do mural, então contar navegações dá falso
positivo de recarga. Usar uma marca em `window`, que só some numa recarga real.
## Miniaturas derivadas + reconciliação do ledger — 2026-09-12
Mural no ar com **13.059 peças** (antes 12.552): commits `bba32f6`,