Achamos cerca de 100 problemas. 13 quebram algo que você pediu. Em 11 desses 13, o defeito está entre dois módulos feitos por agentes diferentes: cada parte funciona sozinha, mas uma não entende a outra.
Novo nesta versão: 🎬 abertura · 🧗 parkour · 🎨 arte · 👥 elenco · 🌆 bairro vivo · 🍩 fase 2 · 🎥 câmera · 🎳 física · 🦸 transformação · 📋 seus pedidos
O seu logo e a sua música, com as fontes de city pop. O 1º toque liga a música e o logo bate no primeiro tempo forte; o 2º começa o jogo.


A camada do título é a do jogo de verdade, por cima de um quadro parado da rua de noite (a máquina estava sem memória para rodar o jogo inteiro no navegador de teste). ⚠️ O japonês do logo diz チュロスクエスト (“Churros Quest”).
A câmera só volta sozinha para as costas do Kavo quando você segura W, que é justamente quando ela já está atrás. Virou com A ou D, o Kavo fica de perfil até você apertar Q/E.


camera.ts:306-359). Com teclado, A e D sempre apontam para o lado.controller.ts:366), inclusive o giro automático do Shift.Prova de que funciona: um agente aplicou as correções 1 e 2 numa simulação com a câmera real do jogo. Depois de virar 90°, ela voltou para as costas em 0,67–1,03 s, sem andar em círculos, e com 40 m de visão à frente. Com Shift+D, o Kavo correu reto por 23 m. O jogo em si não foi alterado.
No vídeo do Threads, o que te pegou foi a jogabilidade e a física. A conversa anterior listou 5 ingredientes. Medimos cada um no código real.
enemies/tune.ts:17).Para chegar no feel do vídeo: ligar o boliche para qualquer corpo acima de ~6 m/s, dividir o empurrão pelo peso de cada inimigo, fazer golpes e corpos empurrarem os objetos da rua, e pôr o pisão e as explosões para empurrar o que estiver em volta.
A sua reclamação: o Kavo fica preso nos prédios, não sobe direito, não tem física, não é fluido e não funciona como Assassin’s Creed.
collision.ts:200Existe biblioteca pronta? Não existe uma biblioteca de parkour estilo Assassin’s Creed para navegador. As peças existem prontas: o controlador do Jolt (para não travar), as animações da Quaternius e o three-mesh-bvh, que já está no jogo, para achar as bordas. O parkour se monta em cima delas.
Você decidiu: o personagem começa como um rapaz comum, se transforma, e só depois da transformação ganha os superpoderes. Abaixo, como os jogos e o tokusatsu fazem isso, e como fica no Kavo. Os números marcados como proposta são meus, para testar jogando.
Um agente conferiu, no código, 248 pedidos e promessas: o que você pediu com as suas palavras e tudo o que o Claude anterior prometeu nos planos 2.0, 3.0 e 4.0.
Dos 44 pedidos feitos com as suas palavras, 31 estão no jogo. O buraco está nas promessas: cerca de um terço do que o Claude anterior recomendou não existe ou não está ligado.
Joguei o build real como se fosse a primeira vez, em 1280×720. Os prints são da partida, sem retoque.









provado = um agente rodou o código real do jogo e mediu. conferido = eu mesmo li o código e confirmei.
energyGain; o combate procura coffee. A atendente usa um terceiro nome, cafe.Não é desleixo de um agente. É o método de dividir o trabalho.
Os 15 trabalharam em paralelo, cada um testando a sua parte sozinha, com peças falsas no lugar das dos outros. O teste do combate, por exemplo, usa um inimigo de mentira.
coffee de um lado, energyGain do outro. Limite 12 de um lado, 120 do outro. O verificador de código não pega esse tipo de erro.
Ficaram anotados em docs/wave2-requests/, e parte deles nunca foi feita.
Ele só confere que o jogo abre, que aparece uma onda e que um golpe acerta. Ninguém jogou o conjunto de ponta a ponta.
Não chega a quebrar, mas tira a graça. Está agrupado pelo momento do jogador.
Nada do que você pediu sai do jogo. O que muda é a ordem: consertar → câmera → parkour → física de grupo → transformação → abrir rápido → ensinar → fechar o ciclo → impacto → testar de verdade → link.
Cada correção é pequena; a maioria mexe em 1 a 10 linhas. O ganho maior vem de um teste do jogo inteiro: comprar café faz a energia subir 1,5×; lançar permite 5 golpes no ar; esquivar no instante liga a câmera lenta. Os nomes passam a ser fixos no código, para o verificador pegar o próximo coffee contra energyGain.
A câmera volta para as costas do Kavo ao virar para qualquer lado. Q/E giram só a câmera. As paredes não empurram a câmera de repente, e com a mira travada há setas para os inimigos fora da tela. A especificação, momento a momento, está na seção da câmera.
Trocar a colisão caseira pelo controlador de personagem do Jolt, que já está no jogo. Subir só quando você pede, com um dono só para o movimento, caminho checado antes de cada salto e animação de verdade. Detalhes na seção do parkour.
Ligar o “boliche” para que um inimigo arremessado derrube os outros, fazer o peso de cada inimigo contar no empurrão, pôr golpes e corpos para mexer nos objetos da rua, e fazer o pisão acertar o que está embaixo. É o que você mais gostou no vídeo.
Um personagem novo para o hominho, o henshin com o coração do peito como relógio, e a 7-Eleven como posto de energia. O Kavo atual continua igual. Detalhes na seção da transformação.
O “carregando” passa a ser a primeira coisa da página. A física vira um arquivo à parte (905 → 742 KB comprimido), que o navegador prepara em paralelo. As fontes vêm dentro do jogo. Os efeitos são preparados antes do título.
Nos dados do Poki, o jogador desiste se o carregamento passa de 10 s. Com 10 MB para baixar, só 70% de quem começa a carregar no Brasil chega a jogar. Poki · W3C/Poki
Um verbo por vez, em 4 tempos: aprender, usar num caso mais difícil, uma reviravolta, provar que domina. Com a transformação, a ordem nasce sozinha: o hominho aprende a andar, pular e escalar; a 1ª onda traz o 1º henshin; depois vêm J → pulo duplo → K → L → mira. A lista de golpes do pause fica como está. E um objetivo sempre visível no canto da tela: a missão ativa ou a próxima onda.
Hayashida (Mario 3D Land) e o kishōtenketsu: Game Developer · Celeste ensina 4 verbos no prólogo · o 2XKO usa J/K/L no PC e mesmo assim confundiu um ex-campeão de Marvel vs Capcom: EventHubs
Ondas com fim e respiro (rodadas e recorde). Uma tabela de preços só. O caixa e a atendente vendendo a mesma lista, com os mangás à venda. Um save só, com “novo jogo”.
O que os jogadores mais sentem como impacto: hitstop, som coerente e câmera. Avisos de ataque claros, no máximo 2 inimigos atacando ao mesmo tempo, inimigos suavizados entre quadros e uma defesa que quebra. Exagerar nos efeitos piora o jogo: efeito médio funciona melhor do que efeito extremo.
Estudo de reviews do Steam (2022) · Sakurai sobre hitstop · fichas de ataque do Spider-Man · efeito médio × extremo
Jogar numa placa de vídeo real, ouvir o som e testar num teclado barato. Mira + W + J + Espaço são 4 teclas ao mesmo tempo, e teclados comuns falham a partir de 3. O controle de videogame já está no roadmap.
O jogo é um arquivo só, e qualquer hospedagem estática serve: Cloudflare Pages (25 MiB por arquivo, grátis) ou itch.io (aceita um .html sozinho). O bloqueio de rede era do outro ambiente; daqui eu publico normalmente, e esta página é a prova. Antes, vale trocar a marca 7-Eleven pela paródia (brand.ts), porque o link fica público.
Como li o seu pedido: a fase 1 é o plano acima, aplicado ao jogo de hoje. A fase 2 transforma o Ato 1 do Filme 1 (cenas 1.1 a 1.10) em jogo. Kavo é o protagonista, no lugar do Kenji. Para o povo, o herói é o “gafanhoto de Tóquio” do jornal da cena 1.9, o que mantém a identidade secreta. O roteiro inteiro está guardado, e nada foi mudado nele.
✅ O conflito da armadura está resolvido pelas suas referências. O Kavo continua vermelho, como hoje. No peito, sai o raio e entra o coração amarelo com 木, que é o Hearto Prism e o relógio da transformação. Veja no elenco.
Sombreado de anime com contorno, cenário Nagai/Sailor Moon e os personagens do elenco. Vem primeiro, para não refazer o resto depois.
Um dia curto na barraquinha (churros → aluguel), a noite com henshin, patrulha e crimes, e a manhã com o jornal.
Os golpes passam a vir do treino, e o platô vira o campo de treino.
As cenas 1.6 e 1.7 viram a abertura do jogo: o tutorial de pulo.
As cenas de dia, com a Chiri e os vizinhos.
As cenas 1.5 e 1.10, curtas, fecham o Ato 1.
A 7-Eleven quebra inteira, e a rua ganha pedestres, carros, motos, arcade center e consequências para quem bate em NPC.
A sua direção: os cenários seguem Hiroshi Nagai com a ilustração de fundo da Sailor Moon dos anos 90. Personagens e objetos interativos seguem Takashi Murakami, Ardhira Putra e o anime Sailor Moon dos anos 90.
⚠️ Conflito com o jogo de hoje. Hoje o jogo é pixel baixo (~270 linhas) com efeito de TV de tubo. A direção nova pede traço limpo e cor chapada, e o pixel baixo borra justamente o contorno e a cor lisa.
Minha recomendação: sair do pixel baixo e desenhar em resolução cheia, com sombreado de anime (cel shading) e contorno. Da TV de tubo fica só um grão leve de VHS, que você pode desligar. Nada muda no visual até o seu “sim”. O Guilty Gear Xrd trocou sprites por 3D com sombreado de anime para sair do limite de resolução e ganhar câmera dramática nos especiais. Motomura, GDC 2015
Primeiro, os personagens com referência sua. As suas imagens valem mais do que as sugestões do roteiro: o Kavo não usa uniforme escolar preto, a Chiri não usa uniforme de marinheiro, e a Aisu é loira, não de cabelo lilás.






Resolve o conflito da armadura: ela fica vermelha, como hoje. A mudança é no peito, que troca o raio pelo coração amarelo com 木. Esse coração é o Hearto Prism e o relógio da transformação. Quando chegar a hora, mostro o antes e depois.
Tudo aqui é sugestão minha, no estilo da direção de arte, e espera o seu ok.
O seu pedido: o cenário quebra (a 7-Eleven inteira, com vidros, prateleiras e produtos), os NPCs podem apanhar, e a rua tem gente, carro, moto e arcade center.
Todos os itens, com arquivo e linha de cada um, separados por relatório. Os que mais importam já estão resumidos acima.
Data: 2026-10-19. Fonte: cópia só de leitura em /tmp/kavo-analise/apps/rubro-arena. Os caminhos abaixo são relativos a essa pasta.
Legenda: 🔴 quebra · 🟠 atrapalha a diversão · 🟡 polimento
src/combat/*, src/enemies/{index,enemy,brain,tune,projectile,ragdoll}.ts, src/camera.ts, src/time.ts, src/events.ts e src/main.ts.contracts.ts, input.ts, audio.ts, fx.ts, docs/plano-3.0.md e docs/wave2-requests/{enemies,controls,fx,animation,render}.md.npx tsc --noEmit passa limpo e npx vitest run passa 26/26. Os testes do Fighter usam um inimigo falso (src/combat/fighter.test.ts:33-44), por isso não pegam os itens 1, 2 e 5.Fighter e a classe Enemy reais num script fora do repo, com vite-node e sem editar nada. Os resultados aparecem como Prova.camera.ts:638 põe a câmera 2,2 m atrás, 0,8 m à direita e só 0,3 m acima da cabeça. main.ts:61 marca o Kavo com camFade = false, então ele nunca fica pontilhado. Ver item 16.time.ts:12-18. Há um problema mais grave por baixo: a esquiva perfeita nunca dispara contra golpe corpo a corpo (item 2).camera.ts:556: uma malha mesclada com mais de 3,5 m de largura vira "parede", não "prop". Solução: pôr userData.camFade = true nas malhas de placa, ou separar as placas do batch.enemy.ts:1278-1283 só desenha "PIIII!" com fx.word e não emite evento, então o áudio não tem gancho. Falta um emit({ type: 'whistle', pos }) por policial.Onde:
src/enemies/tune.ts:26 (juggleMax: 12), src/enemies/enemy.ts:302-305 e :405;src/combat/rules.ts:14 (juggleMax: 120) e src/combat/moves.data.ts:51-63 (custos de 4 a 12 por golpe);src/combat/fighter.ts:914 manda juggleCost: d.juggle cru para o inimigo.O que o jogador sente:
plano-3.0 §2.2 ("J J J → C+J cravada") não fecha;Prova (Enemy real):
| Golpe | Pontos de juggle | Efeito |
|---|---|---|
| lançador | 0 | |
| air1 | 6 | |
| air2 | 12 | |
| air3 | 19 (> 12) | hitstun cortado para 0,10 s e gravidade ×1,6 |
0,1 s depois do air3, teching = true.
Correção:
dropped no planHit, com limite 120;receiveHit de HitInfo2, o inimigo ignora TE.juggleMax, ou passa a usar TE.juggleMax = 120;enemy.ts:1169 faz if (!pl.invulnerable) pl.fighter.hurt(...); o mesmo padrão aparece em enemy.ts:1199, enemy.ts:1241 e enemies/index.ts:487;main.ts:101 passa invulnerable: fighter.invulnerable;fighter.ts:181 e fighter.ts:1073 deixam a esquiva invulnerável nos primeiros 0,25 s;hurt(), em fighter.ts:1224.hurt();fighter.ts:1095-1100).invulnerable === true em todos.fighter.hurt() sempre; ele já testa, nessa ordem, parry → esquiva perfeita → invulnerável;!pl.invulnerable dos 4 lugares;missed pelo retorno de hurt().'coffee' e 'isotonic' (fighter.ts:188, fighter.ts:448, fighter.ts:792, fighter.ts:928);'energyGain' e 'dodgeRecover' (src/interact/items.ts:9-11, :41, :43, :52-53);'cafe', 'isotonico' e 'oden' (src/npc/index.ts:46-48).type BuffId = 'energyGain' | 'dodgeRecover' | 'oden' em contracts.ts e tipar Buff.id: BuffId;fighter.ts:1428-1445: burstWave nunca marca c.landed;moves.data.ts:76: a Explosão é tier 3 com cancel: [], e moves.data.ts:94 (canCancel) não tem tier acima de 3;fighter.ts:331: o atalho do turbo exige c.landed;fighter.ts:1421 zera só o estado do lado do combate;fighter.ts:895-897 reimporta e.limits com ||= no golpe seguinte;enemy.ts:299-302 recusa o 2º quique com base no próprio limits, que ninguém zera.burstWave, this.cur.landed = true quando algum inimigo for atingido;refreshLimits() no Enemy (no contrato IEnemy2) e chamar junto com o do combate;fighter.ts:918 ignora o retorno de receiveHit;fighter.ts:845 marca landed;fighter.ts:926-929 soma combo, energia e hitstop;enemy.ts:262 (levantando, invulnerável) e enemy.ts:267-269 (caído, sem OTG) retornam false;docs/wave2-requests/enemies.md §2. O contrato já foi trocado (contracts.ts:432), mas o combate nunca usou.strike(): const ok = e.receiveHit(h) !== false; if (!ok) return false;;checkHits só marca landed quando ok;fighter.ts:365-367: o decide() testa o Vanish antes da guarda;fighter.ts:1115-1120: janela de 0,35 s, com pelo menos 1 barra;fighter.ts:1223: o parry exige estar em guard.vanish, energia 40 → 10, e o golpe de 9 entra (HP 100 → 91).attackImminent (já existe, enemy.ts:196-201) ou houver um tiro chegando, L vira guarda/parry;fighter.ts:465-468. Com if (this.energy < def.cost) { this.empty(); }, o golpe começa assim mesmo.kiRush.decide() antes do start(), como já é feito com o super, e devolver false;empty().enemy.ts:1204-1213: marca liftT = 0.55 e escreve "AGARROU!";enemy.ts:1235-1245: stepLift chama hurt(22) sem checar a distância;enemy.ts:1067: o liftT nunca é zerado em receiveHit, toAir ou toRag.grabbed no Player via IPlayer2 (trava o input, põe o Kavo acima do Bruto com place e libera com uma fuga, como amassar L);liftT em qualquer reação.main.ts:236 faz player.enterState('fall', hz.speed * 0.4);fighter.ts:1249 vira o Kavo de frente para o golpe;controller.ts:227-229: o enterState zera a ação;fighter.ts:227 vê a ação diferente e cancela o knockdown.fighter.hurt() cuidar: ele já põe actionVel = d * 7 e vy;p.actionVel.copy(d).multiplyScalar(hz.speed * 0.4); p.vy = 9, sem enterState.time.ts:12 sempre faz slowScale = scale e slowLeft = max(...);time.ts:15-18 tem uma escala só;fighter.ts:537, :930, :1182, :1200, :1317, :1423 e em main.ts:176-179.time.ts: scaleFor('world') e scaleFor('player');world (inimigos, projéteis e física);fighter.ts:240 faz consume(b, 220) em milissegundos reais, e fighter.ts:247 faz pressedWithin('jump', 60);input.ts:238-248 usa performance.now;buffer.ts:26 diz que o buffer é em tempo de jogo e congela no hitstop, mas a janela de fora não congela;moves.data.ts:47, super: 0.2 × 1,2 no beat, fighter.ts:910) e a até 0,6 s com C.hitstop = 3.consume/pressedWithin medirem em tempo de jogo, passando o gameTime do main.ts;InputBuffer dele, que já é em tempo de jogo.enemy.ts:1081-1086: o estado engage corpo a corpo não tem limite de tempo e só larga a ficha com d > 11;brain.ts:117: só inimigo FORA da tela rouba ficha;enemy.ts:661: o ragdoll sem K.O. só levanta com clear < 0.9 e não tem prazo;enemies/index.ts:106: a onda só termina (cleared) com zero corpos na rua.engage: depois de ~2,5 s sem atacar, dropTokens(); LOST; atkCd = 1, como o atirador já faz em enemy.ts:1267;ragT > 6: teleportar para o chão mais próximo e levantar (ou remover).src/enemies/look.ts:261-263: o "!" e o visor piscando duram o windup inteiro (0,5–0,9 s);attackImminent (enemy.ts:196-201) só é lido pela câmera (camera.ts:519);audio.ts:384 toca enemyWindup sem posição, embora o evento traga pos;config.ts:18).attackImminent vira true;s.enemyWindup(t, e.pos) passando pelo Panner3D.fighter.ts:1151-1161: o blink usa p.place, que não testa colisão (controller.ts:193-196). O comentário do código diz o contrário;fighter.ts:1138-1142);fighter.ts:1337-1340.place, testar collision.capsuleFree (já existe em collision.ts:261) no destino;fighter.ts:163-177 expõe comboLeft, counterReady, turbo e chargeLevel, mas nem main.ts:287-290 nem o HudSource (hud.ts:7-21) leem esses campos;main.ts:141 e :153), embora src/ui/index.ts:17 documente ui.hint('launcher', ...);MOVE_LIST em moves.data.ts), e só o L tem 5 significados.comboLeft), um "J!" de 1 s quando counterReady e o anel do turbo em volta da barra;camera.ts:638 põe a câmera 2,2 m atrás, 0,8 m à direita e só 0,3 m acima de uma cabeça a 1,45 m;main.ts:61 faz rig.root.userData.camFade = false;camera.ts:646).d*1.6 + right*1.15 + (0, 0.55, 0), com a mira 0,45 m para o lado dela;dialog, pontilhar a cabeça e as antenas do Kavo (setCamFade no grupo da cabeça).main.ts:243-245 interpola só o player (prevPos → pos);enemy.ts:1465 sincroniza a malha com o rag.p do último passo;enemies/index.ts:276.rag.prev no começo do passo;prev → p com o mesmo alpha = acc/DT antes do syncEnemyMesh;fighter.ts:1229-1240: dano de raspão de 15% com piso de 1 HP (Math.max(1, ...)), +2 de energia por golpe bloqueado e nenhum medidor de guarda;fighter.ts:1172-1193: aparar um TIRO faz cambalear um inimigo qualquer a até 6 m (minDot -1.01), e o tiro segue em frente.pr.reflect(), que já existe em projectile.ts:29.enemies/index.ts:115-126; a vida está em enemy.ts:160-162 (+3 HP por onda, chefe ×2,5).floor((w-1)/3) dá 0 na onda 3. E chega junto com um Bruto chefe (≈398 HP, superarmadura), 2 atiradores, 1 voador e 1 capitão, sem nenhum recruta comum;src/audio/beat.ts:42-45; janela de ±0,09 s (config.ts:23-24) a 108 BPM, o que dá 32% de chance por acaso; fighter.ts:903-910.enemy.ts:545-590 e no contrato (contracts.ts:434-435), mas só o demo chama (src/demos/enemies.ts:227). O startGrab (fighter.ts:960-973) não olha a capa;CANCEL do brain nunca é enviado (brain.ts:15, :39);frozen() (time.ts:21) não é usado;toLegacy e o caminho wave-1 (fighter.ts:90-100, :918) existem sem nenhum inimigo wave-1;FAST tem nomes mortos (main.ts:29: flyKick, diveAtk, special).startGrab, se e.canCapeGrab?.(p.pos), chamar capeGrab(p.pos, intent ? 'throw' : 'cover', dir); apagar o resto.fighter.ts:1128-1130 faz burst, afterimage e som; o evento repete tudo em fx.ts:430 e audio.ts:453 (com outra chave de trava, vanish contra vanish-ev);fighter.ts:1184 e fx.ts:425 disparam dois parryFlash;enemy.ts:412-416 e em fx.ts:386 (via fighter.ts:936);fighter.ts:1431-1433 e fx.ts:446 criam duas ondas de choque.camera.ts:241 soma até 1,2).src/combat/projectiles.ts:37-61 não faz raycast no mundo; o tiro do inimigo faz (enemies/index.ts:464);fighter.ts:1374-1408) não tem oclusão.col.raycast(pos, dir, stepLen) dos tiros inimigos;to no primeiro acerto de collision.raycast.fighter.ts:436: andar na direção do alvo + K dá investida, nunca bola;fighter.ts:393-394: J×4 sempre lança e sobe sozinho, mesmo cercado;enemy.ts:1177: o golpe inimigo só pega até 1,1 m de altura, então o ar vira zona segura.ragdoll.ts:101-105 e :126-130 fazem 15 floorBelow e 15 pushSphere por inimigo por passo, em TODOS os estados, até de pé (enemy.ts:735). Com 14 inimigos são ~420 consultas BVH por passo;enemy.ts:1312-1425 cria arrays literais for (const j of [...]) por inimigo por passo;enemy.ts:505 cria um _d.clone() por pino da capa por passo;main.ts:101 (getPlayer()) cria um objeto novo por chamada, várias vezes por inimigo por passo;fighter.ts:830 cria um new Vector3 por passo de golpe ativo;camera.ts:622 cria [...camFaded()] a cada frame.const no módulo;getPlayer devolvendo sempre o mesmo objeto, só atualizado.Para cada um: um teste no vitest com as classes reais Fighter e Enemy (o mock atual esconde os itens 1, 2 e 5).
Escopo: src/player/** (controller, movement/, parkour/, animator, anim/), src/collision.ts, a cápsula do player em src/physics/index.ts, as partes do player em src/main.ts e src/input.ts, os docs de movimento, tests/e2e/movement.mjs + harness.mjs e docs/wave2-requests/*.md.
Método:
/tmp/kavo-analise.npx tsc --noEmit passou limpo e npx vitest run passou 26/26. Nenhum desses 26 testes cobre movimento, parkour ou colisão.Player real no Node (Collision + Parkour + controller verdadeiros, sem WebGL), na mesma pista do harness: chão, bloco de 3 m com a face oeste em x=20, mesmo __place..local-state/kavo-analise/agentes/sim-movimento/audit/*.test.ts.src/ do app para a pasta, crie o link node_modules, depois npx vitest run.Legenda: 🔴 quebra · 🟠 atrapalha a diversão · 🟡 polimento.
src/player/movement/params.ts:32-88:controller.ts:791-801).longJumpGrav 0.7 (params.ts:65) dá 1,39 m de altura em vez de 0,89 m. A linha do teste fica vermelha para sempre; o alvo precisa ser atualizado (item 24).harness.mjs:190-199 varre player → pk → collision e troca a colisão do Parkour também.__place, explicado no item 3.jump1.main.ts:217-222 faz gdt = clock.tick(rdt) e o acumulador do player.step recebe o tempo escalado.releasedWithin('crouch', 90) e consume(..., 400) usam tempo real (input.ts:238-253), então elas mudam de tamanho relativo durante o slowmo.Onde:
controller.ts:544-575 (tryLedge, 0,25–1,2 m), states.ts:73, estados ledgeHang/ledgeClimb (states.ts:232-242).parkour/index.ts:320-327 (mantle 0,3–1,3 m, hang 1,2–2,4 m).controller.ts:297) e só quando há direção para a parede (index.ts:311).O que o jogador vê: o mesmo pulo contra o mesmo muro cai num de dois "pendurados" diferentes, conforme a altura e se a tecla está segurada. Os controles também são diferentes:
| Pendurado SM64 | Pendurado do parkour | |
|---|---|---|
| Andar pela borda (shimmy) | não tem | tem |
| Quina e salto entre bordas | não tem | tem |
| Pose | uma | outra |
| Câmera de pendurado | não liga (item 17) | liga |
Pior caso: no ledgeHang do SM64, Espaço é capturado pelo parkour antes do estado.
tryParkour não exclui ledgeHang (controller.ts:447).plan() trata KIN como chão (index.ts:262) e cai no catLeap (index.ts:290).Evidência (sim):
ledgeClimb/ledgeHang do SM64 no frame 0; 1,3–2,3 m vira hang do parkour no frame 3.ledgeHang>catLeap>hang, com o pé ainda 2,1 m abaixo do topo. Segundo Espaço: hang>mantle>idle.Correção:
tryLedge/ledgeHang/ledgeClimb.planAir com into || vy<=0).'ledgeHang' na lista de exclusão de tryParkour (controller.ts:447), para Espaço chegar em states.ts:237.controller.ts:195: place() faz latchYaw = facing.yaw = facing + π (camera.ts:220).controller.ts:369-370).fighter.ts:1157 chama place) ou um respawn do killY (controller.ts:338), o Kavo corre ao contrário, ou de lado, até soltar ou trocar a tecla. Com a mira travada não acontece, porque lock() refaz o latch.latch.test.ts): W = +x antes. Depois de place(+2 m, mesma frente), a intenção vira (−1, 0), o oposto. Com a frente girada 90°, vira (0, 1).place(), this.lastDirKey = 0; this.lastCamYaw = NaN; em vez de latchYaw = facing. O próximo passo trava no yaw real da câmera.__place, não o swaptests/e2e/harness.mjs:257-262 chama p.place(feet, facing) sem resetVel. Por isso state, grounded, parkour (o PkMove), floorY e o latch vazam de uma linha da tabela para a próxima.params.ts:15, controller.ts:601-608), não a sonda aérea de 0,30 m.place(..., true) mede 0,30 m (PASS).hang do parkour, que é contínuo e nunca termina sem input, place() não zera player.parkour. O parkourStep devolve o corpo para o hang antigo.p.place(v, f, true), chamar r.parkour.reset() e forçar o re-latch (item 2).fighter.ts:1133-1142: o destino fica 1 m atrás do alvo, sem checar colisão.fighter.ts:1151-1160: o blink chama p.place(). O comentário diz "collision still applies, unlike place()", mas o ramo com place é sempre o usado.controller.ts:193-204: place() não valida nada.collision.ts:201: a cápsula ignora faces vistas por trás, então quem fica dentro de um sólido não é empurrado para fora.collision.sphereCast(alvo → dest, RAD) e encurtar até hit.distance − RAD.capsuleFree. Se não couber, cair do lado do Kavo.warpTo(dest, 1/60), que integra paredes (controller.ts:391-409).startDive() (controller.ts:938).diveAtk só existe na tabela wave-1 (contracts.ts:223). O combate wave-2 (src/combat/) não tem nenhuma ocorrência de diveAtk ou startDive.dive, belly, rollout e getup (states.ts:160-171, 198-204) e os clips correspondentes são código morto.ui/moves.ts:25 anuncia "Pule de cima de um inimigo para renovar o pulo duplo", mas nada fora do player zera usedDouble no contato com inimigo.fighter.attackMove(): J no ar sem alvo a menos de 4 m e com |speed| > 6 chama p.startDive().enemies.update, pé sobre a cabeça com vy < 0 → p.vy = 9; p.usedDouble = false. Se não for implementar agora, tirar a linha da lista.states.ts:64-67: a prioridade no ar é wall kick (só depois do contato), depois coyote, depois duplo.controller.ts:810-817: canDouble() olha só o chão, não a parede.kick.test.ts, parede de 10 m):jump4>wallSlide, sem chute.wallKick.canDouble() o mesmo que já existe para o chão (doubleMinFloor). Um raio a pés+1,0 m na direção do movimento, com alcance max(0,6, |speed|·0,1); se encontrar parede de frente (|n.y| < 0,3, -f·n > 0,7), devolver false. O aperto fica no buffer de 0,15 s e o chute sai no contato.parkour/index.ts:306: dispara com (wantUp || upHeld), e o Espaço ainda está segurado no frame da decolagem.parkour/index.ts:471: aceita ângulo 0°, ou seja, correr paralelo à parede.wallrun.test.ts):jump1>wallRunSide.wantUp fresco), ou Espaço segurado por 0,2 s depois da decolagem.controller.ts:1024-1028: o aperto bufferizado dispara dentro de landDefault via groundJump e pula o tryParkour.controller.ts:297).jump2>ledgeClimb.vault>run.pk.plan(..., wantUp=true) antes do groundJump.chainKind 1/2 e chainWindow), o parkour de chão não pega o Espaço.parkour/index.ts:600-602. No ar basta dh < 0,85 do eixo e a intenção para frente.street.ts:71-72, z = ±4,2), justamente onde se corre e se briga.run>jump>poleSwing.dh < RAD + ft.r + 0,1 (~0,6 m), com velocidade fechando no eixo acima de 2 m/s.jumpKind 3 nem logo após a decolagem (< 0,1 s).controller.ts:609-611 devolve x/z mas mantém speed.animator.ts:544: quando a velocidade medida é ~0, o animator confia em p.speed e roda o ciclo de corrida, com passos e eventos step (som).steep.test.ts, 50°): por 2 s, state=run, speed=8.60, deslocamento 0,000 por passo.wallN igual à normal horizontal da rampa e hitWall = true, para o wallHit sangrar a velocidade (controller.ts:693-698).collision.ts:131-137: addDynamic só é chamado no construtor.interact/carry.ts:60, 274: o objeto sai do BVH ao erguer ou chutar e nunca volta depois que assenta.settle() do carry: object.updateMatrixWorld(true); ctx.collision.addDynamic(mesh). O bake é por mesh e barato.addDynamic no ICollision.tuning.ts:77-82: o localStorage sobrescreve os defaults do código.tuning.ts:118-124: ao mexer em um slider, salva o objeto inteiro. A partir daí, mudar jump1 no código não muda nada naquele navegador.tuning.ts:5-46, 128-137) não são lidas por ninguém (grep: zero leituras de T.jump1, T.coyoteTime etc.). Têm os mesmos rótulos da pasta real "Movimento" ("1º pulo", "buffer do pulo", "tolerância da beirada").tuning.ts:142) só restaura T, não as pastas registradas.wallKickWindow, wallKickSpeed, chordFrames, runState, airBack, wallSlideSpeed, diveBoost, sidehop*. Há também números mágicos em states.ts (1,6 / 4,5 / 5,5 / 0,14 / 0,32 / 0,36 / 0,18 / 0,42 / −30...).parkour/index.ts:276: alcance 0,85 + 0,12·v, até 1,9 m.ui/moves.ts:8-27): faltam mortal lateral, deslize na parede, saltos entre bordas, wall run lateral e o atalho Espaço→C de 3 frames.intent·n < −0,5) e fora da janela da corrente.controller.ts:363-370. Só segue a câmera quando ela gira a mais de 1,9 rad/s.camera.ts:299, 0,006 rad/px) a 150 px/s dá 0,9 rad/s e é ignorado enquanto a tecla está segurada.manualYawDelta por frame (Q/E + mouse). O latch ignora só o auto-giro e o escape de parede, e passa todo giro manual.street.ts:156, 176: o fio visível cai 0,55 / 0,45 m no meio.street.ts:132: guarda points e sag.features.ts:59-61: usa só path[0] e path[last], em linha reta.index.ts:632-637: o equilíbrio segue essa linha.stepBalance, a altura vira lerp(a,b,u) − sag·4u(1−u), ou interpolar userData.points. Usar a mesma curva no teste de pegar.index.ts:211);index.ts:357);params.ts:86);controller.ts:556).idle, o settle() falha (controller.ts:492) e ele escorrega.WALKABLE_NY em params, usado por todos.main.ts:270-272. O teste player.state === 'ledgeHang' está dentro do ramo pk ? …, e no pendurado SM64 player.parkour é null. Então o teste está morto e a câmera fica no modo default.(pk && HANG.has(pk.kind)) || player.state === 'ledgeHang' ? 'hang' : pk ? 'parkour' : …, ou o item 1 resolve.index.ts:366-370);index.ts:911);index.ts:941, 1093);index.ts:665).capsuleFree ou sphereCast por chave do warp no plan. Se falhar, recusar o movimento ou encurtar.physics/index.ts:269-289, 333-337 e main.ts:279.setPlayerCapsule recebe HALF constante, então nunca agacha. O controller também mantém a parede até 1,85 m em crouchSlide/belly (params.ts:21).MoveKinematic roda por tick do Jolt. A 30 fps a velocidade vista pelos props dobra num tick e zera no seguinte.HALF reduzido em crouch/slide, chamar setPlayerCapsule dentro do loop fixo e escolher a dupla de props que bloqueia o player (caixas grandes no BVH).RAD, HEAD, HANG 2.1 e OFF_WALL 0.42 próprios em parkour/index.ts:16-21;hangDepth 2.12 e o offset RAD + 0.05 no SM64 (controller.ts:572);speeds = { walk 3.2, run 8.6, sprint 11.5 } fixo no animator (animator.ts:65), sem ligação com MT.runSpeed, então o pé desliza se a corrida for ajustada.PkMove: parkour/index.ts:67, movement/parkour-link.ts:28 e anim/parkour.ts:166 (este com [k: string]: any);ParkourInput;docs/wave2-requests/parkour.md para pôr step/landing/edgeDelay/reset no IParkour não foi aplicado, daí o cast as unknown as PkApi (controller.ts:152).anim/parkour.ts testa typeof m.dir === 'number' (animator.ts:465), mas dir é Vector3.body.ts com as medidas da cápsula, importado por params, parkour, animator e main; um único PkMove em contracts.ts.cost.test.ts): 5 raios + 4 varreduras de cápsula por passo correndo no plano; 6 + 4 caindo contra muro alto.planTagged faz new Vector3 por feature por passo (index.ts:565); com ~60–80 features em Tóquio, são ~4–5 mil objetos/s;player.feet(new Vector3()) (index.ts:260) e fwdOf(..., new Vector3()) (index.ts:268, 304, 562) a cada passo;Collision.raycast clona ponto e normal em todo hit (collision.ts:156), mais o objeto interno do three-mesh-bvh;capsuleContacts cria closures e um objeto literal por BVH por passada (collision.ts:194, 229-233);tmpV.clone() a cada frame (animator.ts:737).raycastInto(out) sem clone;main.ts:223-224: o fighter roda antes do player.controller.ts:380) e, no ar, C inicia groundPound, cuja entrada zera speed, lat e vy (states.ts:70-72, 209).fighter.ts:490).crouch/crouchSlide por 1–3 frames antes do lançador.CHORD_MS): marcar poundPendingT e só entrar em groundPound se não vier J/K nesse intervalo.controller.ts:439-442: uma exceção no parkour (pkFailed) desliga o parkour pelo resto da sessão, só com um console.error.controller.ts:338 (respawn do killY) não chama pk.reset() nem camRig.snapTo (só o restart faz, main.ts:158-160).controller.ts:533: o ledgeClimb força grounded = true mesmo com settle() falhando, o que dá um pulo de coyote grátis no ar.controller.ts:152 + main.ts:57-59: o construtor do Player cria um Parkour que escaneia todos os meshes e é jogado fora.street.ts:111-115: cada poste vira 2 features (o solid tagueado e o marker), o dobro no loop do item 21.grounded = settle();pk opcional no construtor;path.Player no Node em segundos. Hoje a única rede de segurança é o e2e do browser, que tem o bug do item 3.params.ts:69) contra ~0,167 s no SM64 (5 frames a 30 fps, conforme docs/movement-audit.md).src/player/movement.test.ts com as cenas desta auditoria: borda, probe, latch, chute, wall run, poste, rampa;controller.ts:794, 800. lat = −airSide·k·sin(d)·m é atribuído direto, não acumulado; com m < 0,1, lat = 0. Com teclas digitais, D no ar vira 2,4 m/s de lado na hora e solta para zero na hora.lat = approach(lat, alvo, 12·dt) (~0,2 s para 2,4 m/s). Manter o "não acumula além de 2,4 m/s".| Item | Kavo hoje | Referência |
|---|---|---|
| Coyote | 0,10 s (params.ts:53). Sim: funciona até +6 frames |
Celeste usa uma graça curta (Thorson); guias citam 0,05–0,15 s. SM64 não tem |
| Buffer de pulo | 0,15 s por botão, em tempo de jogo (controller.ts:183-186) |
Celeste: o pulo bufferizado sai no 1º frame no chão. Problema daqui: o buffer pula o parkour (item 8) |
| Wall kick | 0,14 s depois do contato; antes do contato vira duplo (item 6) | SM64: 5 frames a 30 fps ≈ 0,167 s (docs/movement-audit.md) |
| Controle aéreo | sem girar; lateral liga/desliga (item 25) | SM64 analógico; AHiT com inércia |
| Parkour para cima | Espaço por contexto, até 1,9 m à frente (item 13) | AC Unity: modificador segurado + cima/baixo; DL2: tudo no pulo, sem triplo competindo |
| Leitura da animação | 2 poses de pendurado (item 1); corrida no lugar (item 10); mergulho sem gatilho (item 5) | — |
Ukikipedia devolveu 403 neste ambiente. Os valores do SM64 vêm de docs/movement-audit.md (trechos de Ukikipedia que o agente anterior coletou).
Auditoria de 19/10/2026, feita só lendo código da cópia /tmp/kavo-analise/apps/rubro-arena. Não abri navegador. npx tsc --noEmit passou limpo e npx vitest run deu 26/26, mas nenhum dos 26 testes cobre as áreas deste relatório (NPC, missões, carteira, lojas, input, UI, áudio). Também rodei um script em node (vite-node) que importa brain.ts, items.ts e products.ts para conferir preços e a progressão da amizade. Os números da tabela de preços e da progressão vêm dele.
Legenda: 🔴 quebra (algo prometido não funciona) · 🟠 atrapalha a diversão · 🟡 polimento
A cidade é bonita e o combate está ligado, mas a economia não funciona como prometido: bebidas de efeito não fazem nada, os mangás e o caixa não podem ser usados, os golpes desbloqueados somem ao recarregar e os preços estão espalhados por 6 lugares. Para quem joga pela primeira vez, não há tutorial nem objetivo visível, e as ondas não param nunca.
| # / § | O que o HANDOFF diz | Veredito |
|---|---|---|
| §7 #2 | NPC via Claude desligado | Confirmado, e pior. useCapability depende de window.claude (src/npc/runtime.ts:38-47). No link "sem Claude" que o dono quer (§8), a fala livre nunca vai usar o modelo. Mesmo assim o jogo convida a "Enter fala livre" (src/npc/index.ts:394, src/npc/dialog.ts:246) |
| §7 #3 | Áudio nunca ouvido, fontes nunca testadas | Continua igual. As fontes ainda vêm do Google Fonts (index.html:8-10); offline, cai em Arial Black |
| §7 #4 | Câmera do diálogo: as antenas tapam a atendente | Confirmado pela geometria (item 14) |
| §7 #6 | Falta o som do apito | Confirmado, e pior: além de não existir whistle() em src/audio/world.ts, ninguém chama play('whistle'). Enemy.whistle só desenha "PIIII!" (src/enemies/enemy.ts:1278-1282) |
| §7 #7 | Preços diferentes | Confirmado, e pior: são 6 fontes, os efeitos divergem e 4 itens de efeito não fazem nada (itens 1 e 4) |
| §7 #9 | O noren perde as letras | Confirmado: ClothOpts não tem map (src/contracts.ts:468) e o noren estático é escondido quando o tecido carrega (src/world/tokyo/index.ts:311) |
| §7 #11 | Placas mescladas sem dither | Não reverifiquei (é render/câmera) |
| §5.4 | "Golpes desbloqueáveis: tatsumaki, raio-duplo (mangás)" | Refutado no jogo montado: não dá para comprar os mangás (item 2) |
| §5.9 | "Bicicletas, cones e lixeiras também podem ser erguidos" | Refutado: só as 2 bicicletas têm userData.pickup (src/world/tokyo/street.ts:274). Cones e lixeiras são corpos de física e não dá para erguê-los |
| §5.10 | Missão "gangue na porta" | A gangue não aparece na porta (item 5) |
| §5.7 | "chuva e poças" | A chuva começa desligada (rain: 0, src/world/tokyo/index.ts:33) e, ligada, não tem som (item 21) |
src/interact/items.ts:41,43,52,53 dão os buffs energyGain / dodgeRecover. A atendente usa cafe, isotonico e oden (src/npc/index.ts:46-48). O combate só lê coffee e isotonic (src/combat/fighter.ts:188,448,792,928).coffee e isotonic em items.ts, ou traduzir em Fighter.addBuff. Um teste no vitest, item por item: aplicar o efeito e conferir que fighter.buffs mudou.src/interact/index.ts:50-54 marca o caixa como suppressed quando há um NPC a menos de 2,5 m. O caixa fica em (4,4; -8) e a Sra. Hoshino em (5,6; -8), a 1,2 m. Com isso, o prompt fica nulo (src/interact/shop.ts:29) e use não faz nada (shop.ts:31). O estoque dela (src/npc/brain.ts:10-16) só tem onigiri, oden, café, isotônico e onigiri secreto. Os mangás aparecem na estante (src/world/tokyo/konbini/interior.ts:237), mas productSlots não vira nada que se possa usar: só a demo os lê.SHOP), vendidos por shopSell. A estante pode virar um objeto de uso, "pegar mangá → leve ao caixa", que abre a conversa já nessa página.Fighter._unlocked só existe em memória (src/combat/fighter.ts:147,191). kavo-unlocks-v1 é gravado (src/interact/items.ts:114-115), mas nunca é relido para o lutador; só serve para mostrar "já aprendido". A memória da atendente grava taught: true (src/npc/brain.ts:137) e canTeach exige !taught (brain.ts:77).main.ts, chamar fighter.unlockMove para cada id de kavo-unlocks-v1 e, se brain.memory.taught, também para palma-hoshino. Mais um teste de "recarregar mantém o golpe".src/npc/brain.ts:10-16 (atendente), src/interact/items.ts:38-46 (máquina), :48-57 (caixa), :63-74 (prateleira), src/world/tokyo/konbini/products.ts:234-252 (KONBINI_ITEMS) e cartazes pintados em src/world/tokyo/konbini/textures.ts:62,69,76. O mapa EFFECTS da atendente (src/npc/index.ts:44-50) é código morto: main.ts:128 passa sell: shopSell, que aplica o efeito do caixa (src/interact/shop.ts:87-95).src/economy/catalog.ts: id, nome, preço, efeito) que todos importam, com os cartazes desenhados a partir dele. Apagar EFFECTS e KONBINI_ITEMS.price. Um teste que falha se o mesmo id tiver 2 preços. (Tabela completa no fim do relatório.)src/npc/quests.ts:88 chama c.enemies?.nextWave() sem at, mas a API existe justamente para isso (src/enemies/index.ts:113-115: "quest: gang at the konbini door"). A conclusão em quests.ts:116-121 vale para qualquer en.cleared. restart() (src/main.ts:155-165) roda enemies.clear() e não mexe nas missões.SPOTS.konbini.door como at, guardar a referência dos inimigos da missão e concluir só quando esses caírem. Em restart(), chamar clerk.quests.clear() e brain.questFail(ativa). E não avançar o contador global de ondas.src/main.ts:153), Shift (main.ts:141) e "I conversar" (src/npc/index.ts:394). O título (index.html:131-135) só fala de luta ("lance as sombras para o alto") e mostra 6 teclas escritas à mão, que não acompanham o remapeamento. "APERTE QUALQUER TECLA" não vale para Enter, clique ou tecla sem função: só ação mapeada inicia (src/input.ts:147-155, main.ts:170). No celular nada acontece e nada avisa que precisa de teclado.main.ts:152). O jogador não sabe que existem ienes, konbini, atendente nem missões. Para dominar o jogo há 11 teclas, 4 combinações e 33 entradas em 6 abas da lista de golpes (src/ui/moves.ts), mais 3 golpes desbloqueáveis que nem aparecem nela (estão em src/combat/moves.data.ts:120-122 e não chegam à UI).rich('{attack}'). Dicas na hora certa: J depois do 1º inimigo perto, mira (Shift) com 2 inimigos, C+J depois de 3 acertos, "I comprar" perto da máquina, "¥ = compras" na 1ª moeda.src/hud.ts:7-23) não tem campo de missão. A missão aparece só em avisos curtos (src/npc/quests.ts:79,86,90), o tempo do oden só em avisos aos 60, 30 e 10 s (quests.ts:109) e a gangue não tem marcador.src/main.ts:229-231: sempre que a rua fica limpa, a próxima onda vem 5 s depois, sem limite e sem tela de fim. Com inimigo a menos de 9 m, a atendente se esconde e fecha a conversa (src/npc/index.ts:396-407). Não se guarda recorde.hitTest. Vale a esfera padrão de radius × 0,6 = 1,32 m em volta do peito dela (src/interact/index.ts:97, src/npc/index.ts:431). Todo golpe testa objetos de cenário (src/combat/fighter.ts:849-852), mesmo depois de acertar um inimigo. O resultado é amizade −30, 40 s sem conversa e polícia chamada (src/npc/index.ts:364-380).hitTest que só vale atrás do plano do balcão (x > 4,68) e só para golpes que não acertaram inimigo. O 1º toque vira bronca ("Ei! Cuidado!"); a polícia só vem no 2º golpe em até 3 s.world.update(rdt, …) (src/main.ts:278) chama crossing.update(dt) (src/world/tokyo/index.ts:369). Já o teste de atropelamento roda no passo de jogo (main.ts:235-236). No acerto, fighter.hurt vira o Kavo de frente para o impacto (src/combat/fighter.ts:1252) e depois enterState('fall', 9,6) apaga a ação e o empurra para a frente (src/player/controller.ts:227-230), ou seja, de volta para o trem.gdt para a passagem de nível (world.update(rdt, feet, gdt)). Depois de hurt, fazer player.facing = atan2(hz.dir.x, hz.dir.z) antes do enterState, ou aplicar a velocidade em actionVel. Opcional: aviso no HUD quando o jogador estiver a menos de 6 m dos trilhos durante o alarme.crossing tem ambience: 'crossing' (src/world/tokyo/index.ts:248). main.ts:259 chama audio.ambience('crossing') e, como 'crossing' não está em TOKYO_BEDS (src/audio.ts:65), audio.ts:171-172 liga a trilha "arena". O evento zone também chama ambience, com outra regra (audio.ts:428-432), então são duas chamadas que brigam.ambience: 'crossing' por 'street' (os sinos já vêm do evento train) ou incluir 'crossing' em TOKYO_BEDS. Deixar um único caminho: só main.ts ou só o evento.init() guarda a promessa de Tone.start() e devolve sempre a mesma (src/audio.ts:102-118). Ela é chamada a cada onPress (src/main.ts:168), inclusive no Esc e no "pause" falso disparado por blur ou troca de aba (src/input.ts:123-125). Pela regra do navegador, Esc não conta como gesto do usuário.resume() fora de um gesto fica pendente, e o código nunca tenta de novo. Clicar com o mouse também não inicia o som.getContext().state === 'running'. Chamar Tone.start() em todo keydown que não seja Esc e em pointerdown, e ignorar o caminho de blur/visibilidade.rubro-tuning-v1 grava o objeto T inteiro em qualquer mudança e depois sobrepõe os padrões do código (src/tuning.ts:58-67). As pastas dos módulos fazem o mesmo (tuning.ts:77-84,120). "Restaurar padrão" só restaura o grupo de movimento, não Som, Interação, Trem, Rua, Konbini, Câmera… (tuning.ts:142).rubro-tuning-*, e o painel deve mostrar um selo "ajustes modificados" quando houver sobrescrita.src/camera.ts:630-648: a câmera fica 2,2 m atrás e 0,8 m à direita da cabeça (1,45 m), só +0,30 m acima, e mira o rosto dela deslocado −0,2 m para a esquerda (camera.ts:638,646). O alvo é a cabeça dela + 0,35 m (src/npc/index.ts:91,295). Pela conta, a linha de visão passa uns 0,25 m à direita do centro do Kavo, a ~1,78 m de altura, onde ficam as antenas.camFade) no modo dialog, ou fazer um contraplano de 3/4 vindo de trás do balcão. Conferir com print.window.claude, freeText cai em scriptedReply (src/npc/script.ts:101-128). O nome capturado é a última palavra da frase (script.ts:108): "me chamo Luciano e quero onigiri" vira "onigiri-san".pop().src/audio/world.ts e play() ignora em silêncio (world.ts:65-70). Enemy.whistle nem chama o áudio (src/enemies/enemy.ts:1278-1282).uiMove, uiSelect, uiBack e pause em world.ts, mas a UI (src/ui/*) não toca nenhum.buy é emitido antes de vend coin (src/interact/vending.ts:120,124 contra a regra em src/audio.ts:406-409). No gachapon acontece o mesmo (src/interact/gachapon.ts:131), e a manivela não tem som.rouletteWin) toca no jackpot, na missão concluída (audio.ts:440) e no golpe novo (audio.ts:472). Os momentos mais importantes não têm som próprio.whistle() (trinado de 2,8–3,2 kHz, 0,5 s) e chamá-lo em Enemy.whistle. Um gancho de som em ListNav, pushModal e popModal. Emitir buy com a origem (source: 'vending' | 'gacha') e pular o bipe nesses casos. Jingles próprios para missão e golpe.ix?.wallet.update(DT) em src/main.ts:227, e de novo dentro de Interactions.update (src/interact/index.ts:172).src/interact/tune.ts:18), e o ímã e a gravidade rodam no dobro da velocidade.e.code (src/input.ts:54-67). Semicolon vira "Ç" (padrão ABNT2) e Backquote vira "" (padrão US), uma mistura dos dois. O diálogo usa e.key para 1–4 (src/npc/dialog.ts:259). O teste de fantasma desenha um QWERTY fixo (src/ui/pause.ts:39-44). Os menus escutam W/S/A/D/I fixos (src/ui/dom.ts:203-210`).navigator.keyboard?.getLayoutMap() para desenhar o rótulo real (com e.code de reserva), e.code Digit1–4 e Numpad1–4 no diálogo, e menus que sigam as teclas mapeadas.lock: ['ShiftLeft','ShiftRight'] (src/input.ts:24). O aviso só aparece depois do 5º toque (input.ts:143-146, src/main.ts:141), quando o Windows já abriu a janela de Teclas de Aderência. No modo "segurar", manter o Shift direito pressionado por 8 s abre as Teclas de Filtragem.src/interact/vending.ts:172,187), multiplicado por 3 máquinas. Elas voltam inteiras a cada recarga, e os ienes ficam salvos (src/interact/wallet.ts:50,153). Uma bebida custa ¥110–160.rain: 0 por padrão (src/world/tokyo/index.ts:33,98). main.ts:261-266 liga a chuva visual, mas nunca chama audio.setRain (src/audio.ts:177). Poças e reflexo dependem de wet, que fica em zero.audio.setRain(weather.rain > 0,1) quando ela mudar.src/interact/vending.ts:318-322: uns 5 s depois da compra, o "ATARI" abre um menu modal, esteja o jogador onde estiver. O jogo pausa no meio da luta.konbini.update(dt, feet) não recebe others (src/world/tokyo/index.ts:370), embora a API aceite (src/world/tokyo/konbini/index.ts:194-199). As folhas da porta não têm colisão.konbini.update.kavo-yen-v1, kavo-unlocks-v1, kavo-gacha-v1, kavo-npc-hoshino-v1, kavo-bindings-v2, kavo-input-options-v1, kavo-volume-v1, kavo-hints-v1 e rubro-tuning-*. No recarregamento, a amizade vira max(5, salva) (src/npc/brain.ts:131), então bater nela e recarregar apaga o castigo. loadCollection não confere o formato: se a chave guardar "null", Object.keys(null) dispara um erro a cada quadro em prompt() (src/interact/gachapon.ts:22-24,108). Volume salvo sem validação pode virar NaN (src/ui/pause.ts:51). O limite de 3 compras por conversa (brain.ts:30,151) obriga a fechar e reabrir o diálogo.kavo-save-v2 com versão e migração, um botão "Apagar progresso" em Opções, a amizade salva como ela está, formato validado na leitura e o limite de compras removido.src/hud.ts:61), "I conversar" (src/npc/index.ts:394), "L na hora certa" e "J e L juntos" nas fofocas (src/npc/script.ts:45-50).hud.ts:60, index.html:133); ela fala de "gangue" e "moleques de capa"; o gachapon, de "inimigos".script.ts:50), mas a barra cheia fica amarela.rich() com {jump}, {interact} e {defense} em todo texto que cita tecla; escolher um nome para os inimigos; no aviso de golpe novo, incluir o comando e acrescentar a seção "Desbloqueados" na lista de golpes.| Item | Máquina (DRINKS) |
Caixa (SHOP) |
Prateleira (SHELF) |
KONBINI_ITEMS |
Atendente (STOCK) |
Cartaz na loja |
|---|---|---|---|---|---|---|
| Oden | — | ¥200 (cura lenta 60/10 s) | — | ¥120 | ¥220 | "70円〜" |
| Café | ¥130 "Café em lata" (30 s) | ¥150 (60 s) | — | ¥120 | ¥130 | ¥120 |
| Isotônico | ¥150 "Sweat" (30 s) | ¥160 (60 s) | — | ¥160 | ¥160 | — |
| Bentô | — | ¥480 | — | ¥550 | — | — |
| Chá verde | ¥140 (cha-verde) |
— | ¥150 (cha) |
¥150 | — | — |
| Onigiri | — | ¥150 | — | ¥150 | ¥150 | (fala dela: ¥150) |
| Karaagê | — | ¥220 | — | ¥220 | — | ¥220 |
| Mangás | — | ¥500 | — | ¥500 | — | — |
O que o jogador consegue usar de fato: só os preços da atendente e da máquina, porque o caixa está bloqueado (item 2). E o efeito que a atendente aplica é sempre o do caixa (item 4).
src/interact/gachapon.ts:13-20) não dá recompensa, e não existe fim nem recorde (8).src/npc/brain.ts:20-24). Depois disso os telhados, as tirolesas e o prédio de 4 andares ficam sem uso.src/world/tokyo/index.ts:255-262) não serve para nada porque não há carros; as portas com noren do outro lado são caixas sólidas; nas duas pontas há barricadas.src/world/tokyo/konbini/brand.ts:31-41 usa o nome, as cores e o logo da 7-Eleven real, mais "SEVEN CAFÉ" e "7P". A bebida "Isotônico Sweat" (src/interact/items.ts:41) lembra Pocari Sweat. A campainha da porta é original (arpejo de Fmaj7, src/audio/world.ts:168-176), não a da FamilyMart. Para uso interno está ok; antes de um link público (§8), trocar BRAND pela paródia.
resume() pendente (item 12 marcado como provável).Auditoria de leitura do código em /tmp/kavo-analise/apps/rubro-arena (cópia, nada foi editado), em 19/10/2026.
/tmp/kavo-analise/build-sm (KAVO_SPLIT=1 vite build --sourcemap). Um script decodifica os mappings e soma os bytes minificados de cada pacote.vite-node (DOM e canvas falsos) e conta o que o renderer enviaria em cada passe. A contagem não tem frustum culling: é o teto de uma onda cheia com tudo na tela.process.cpuUsage() com o Jolt e o recast reais, numa VM compartilhada de 4 núcleos com load average de 49 a 62. Os valores absolutos ficam inflados. A comparação entre eles vale..local-state/kavo-analise/agentes/render-perf-scripts/.| O quê | Valor |
|---|---|
dist/index.html |
6.613.370 bytes · gzip 1.804.805 |
Script inline no <head>, antes do <body> |
6,39 M caracteres. 273 caracteres acima de U+00FF (japonês) obrigam string de 16 bits: ≈12,2 MB de RAM só para o texto do script |
| Draw calls, passe principal (onda de 8, sem culling) | 622 |
| Draw calls, passe de sombra | 260 |
| Kavo | 84 draws (42 peças + 42 cascas de contorno) + 42 de sombra · 7,3 k triângulos |
| Cada inimigo | 37 a 46 draws + 17 a 20 de sombra |
| 3 máquinas de venda + 2 gachapon | 114 draws, 78 materiais únicos |
| Cena de Tóquio | 787 objetos, 152 materiais, ~23 programas de shader (estimativa) + 5 de pós + ~15 de FX |
| Geometria | 130 k triângulos · 6,3 MB |
| Luzes | 1 hemisférica, 2 direcionais (1 com sombra 2048²), 6 pontuais |
| Passes por quadro | sombra + cena 480×270 + 13 passes de pós (pixel, brilho, 10 de blur, 2 de downsample) + CRT em resolução cheia = 15 passes de tela cheia |
Platô (?mapa=plato) |
484 draws, 231 de sombra, 61 k triângulos |
| Pacote | KB | Observação |
|---|---|---|
| jolt-physics (wasm dentro de string JS) | 3.468,9 | 54 % do arquivo |
código do app (src/) |
753,9 | |
| @recast-navigation/wasm (+ core 48 + generators 14 + three 7) | 709,3 (+69) | wasm em base64 de 451 KB |
| three (module 346,6 + core 224,1 + examples) | 590,8 | |
| tone + standardized-audio-context + automation-events | 258,3 | |
| tweakpane + plugin-essentials (painel de dev) | 296,1 | sempre carregado e montado no boot |
| three.quarks + quarks.core | 145,7 | |
| three-mesh-bvh | 46,9 | |
| xstate | 38,8 | |
stats-gl (só com ?dev) |
35,8 | entra inline mesmo sem ?dev |
| gsap, lil-gui, @pixiv/three-vrm, troika-three-text | 0 | nunca importados |
.wasm contra string embutida| bruto | gzip | brotli | |
|---|---|---|---|
jolt-physics.wasm.wasm + wasm.js (cola) |
2.021 + 965 KB | 742 + 136 KB | 507 + 95 KB |
wasm-compat.js (o que o jogo usa) |
3.222 KB | 905 KB | 622 KB |
| Etapa | ms de CPU |
|---|---|
createWorld('tokyo') |
654 |
| Jolt: decodificar e compilar o wasm | 208 (1.542 ms de relógio) |
Jolt addStatic (52 malhas) |
118 |
attachPhysics (10 tecidos + 29 props) |
39 |
| navmesh do recast | 193 |
| BVH de colisão | 64 |
buildRubro |
16 |
buildInteract |
20 |
Fica fora da tabela: a montagem do Tweakpane e a compilação de shader do primeiro quadro, que só o navegador mede.
index.html:10: CSS do Google Fonts que bloqueia a renderização e também a execução do script de módulo;dist, o script inline de 6,4 MB fica no <head>, antes do <body> que contém #loading (index.html:108);main.ts:39-296: main() é síncrono do começo ao fim;main.ts:295: remove #loading no mesmo tick;requestAnimationFrame compila todos os shaders antes da 1ª pintura.<script> para o fim do <body>, ou começar o main com await new Promise(r => requestAnimationFrame(() => setTimeout(r))), para o CARREGANDO… pintar;await entre elas e um texto de progresso: mundo, colisão, navmesh, física;await rp.renderer.compileAsync(scene, cam);src/physics/jolt.ts:1: import initJolt from 'jolt-physics/wasm-compat', estático;main.ts:19 + main.ts:132: o "carrega assíncrono" só adia a instanciação, porque o parse acontece no boot;vite.config.ts:7,11: single-file com assetsInlineLimit de 100 MB..wasm (905 contra 742 KB);build:artifact também não resolve: o chunk principal continua com 5.530 KB (1.552 KB gzip) porque o Jolt vai junto.jolt-physics/wasm com o .wasm como asset (?url) e WebAssembly.instantiateStreaming;import() depois do título;recast-navigation com .wasm separado.src/player/rig.ts:48-52: cada peça é um Mesh com uma casca de contorno filha (anim/look.ts:24-34);src/enemies/look.ts:63-77, 108-125: o mesmo padrão nos inimigos;src/interact/vendingMesh.ts: 114 draws e 78 materiais para 5 objetos.SkinnedMesh com pesos rígidos (1 osso por peça): 1 draw + 1 de contorno por personagem;npm run test:visual, pela regra do Louie contra regressão;InstancedMesh por tipo de peça.compile/compileAsync no projeto (grep vazio);fx.ts:130-146: 4 Points, sprites, speedlines, shocks, beams;fx/bursts.ts:344-363: 4 sistemas quarks;fx/ghost.ts:38;physics/cloth.ts:499: a capa.await renderer.compileAsync(scene, camera), que usa o KHR_parallel_shader_compile, e esconder tudo.src/interact/vending.ts:181: this.mesh.light.visible = false;vendingMesh.ts:243, uma por máquina, 3 no total.light.intensity = 0 no lugar de visible = false. É uma linha, e o rail.ts:169 já faz assim com a luz vermelha.src/shaders/fade.ts:36-55. O clone do material é descartado ao voltar ao sólido (:41).dispose() zera usedTimes e o three apaga o programa camfade;mesh.material de volta, sem dispose;uCamFade nos materiais originais (padrão 0) e evitar o clone.main.ts:243);src/enemies/index.ts:276).main.ts:176-179, 0,3×), o ragdoll é atualizado a ~18 Hz e fica picotado no momento mais cinematográfico.rdt ≤ 0,1 s e no máximo 6 passos (main.ts:214,222,239);physics/index.ts:312-316).rag.p) e chamar enemies.render(alpha = acc / DT) depois do loop, como já é feito com o player;src/world/tokyo/index.ts:289-311: são 5 noren × 2 metades;physics/index.ts:345-349 processa todos os tecidos em todo passo.preStep e com o corpo desativado) quando está a mais de ~15 m da câmera ou fora do frustum;staticNoren, que já existe (index.ts:211, 311).src/fx/ghost.ts:31-50. O filtro só exclui fxOverlay, mas as cascas de contorno usam userData.outline (anim/look.ts:31) e entram.userData.outline, o que corta pela metade;webglcontextlost ou webglcontextrestored no projeto (render.ts:193-242, main.ts).webglcontextlost: preventDefault(), ctx.setPaused('gpu', true) e um aviso na tela;webglcontextrestored: applySize() e despausar;WEBGL_lose_context.src/input.ts:124-125 só solta as teclas;audio.ts não trata visibilitychange;main.ts:278: world.update(rdt, …) roda com o tempo real mesmo com o menu de pausa aberto.blur e no visibilitychange oculto: pause.open() e Tone.getContext().rawContext.suspend(); ao voltar, resume();halted ? 0 : rdt ao world.update.main.ts:303: fatal() procura #loading, que main.ts:295 já removeu, então só sobra o console.error;main.ts:212: o próximo rAF é agendado antes do corpo do quadro.rp.render nunca roda e a tela congela no último quadro;try/catch;index.html:8-10.main() só roda depois do timeout;@font-face base64 (~60 a 120 KB no total), ou <link rel="preload" … onload> sem bloquear.src/tuning.ts:1-2: Tweakpane com import estático, 296 KB;main.ts:140 → tuning.ts:107-150: monta ~230 bindings em 18 pastas com o painel escondido;render.ts:247: stats-gl entra inline mesmo sem ?dev, +36 KB.registerTuning aplica o que estiver salvo no localStorage por cima dos padrões novos (tuning.ts:77-82);reflectAll, pixelHeight 540, substeps 4, clothIterations 16.registerTuning sem importar o Tweakpane;import('./tuning-panel') só na primeira vez que a crase é apertada;package.json: gsap (6,4 MB em node_modules), @pixiv/three-vrm (5,9 MB), troika-three-text (972 KB) e lil-gui (256 KB) nunca são importados e somam 0 KB no bundle;scripts/artifact.mjs gera um dist/artifact.html de 6,6 MB que o HANDOFF §8 diz ser recusado.dist.artifact.mjs (o caminho bom é o build:artifact).src/world/tokyo/index.ts:143-144 e src/arena.ts:70-71.render.ts:394-414: pixel, brilho, 10 de blur, 2 de downsample;render.ts:454 + shaders/crt.ts:89-117: ~10 leituras de textura por pixel, +8 no quadro pop-art.NIGHT.wideBloom) e o vazamento de cor (bleed, 4 leituras).src/fx/points.ts:173.alive === 0, obj.visible = false e sem needsUpdate. No resto, addUpdateRange até o maior índice vivo.main.ts:291 sempre renderiza.rdt hoje pode ficar negativo: falta Math.max(0, …) em main.ts:214.Onde: render.ts:202, 262. Não há ResizeObserver/devicePixelContentBoxSize nem matchMedia para mudança de DPR ao trocar de monitor.
Impacto:
com DPR 3, o buffer sai com 2× e o navegador estica 1,5× com filtro bilinear;
o upscale inteiro, que é o coração do visual, vira borrão desigual.
Os casos comuns estão corretos, conferidos com o algoritmo de render.ts:273-278:
| Tela | Escala | Alvo baixo |
|---|---|---|
| 1366×768 | 3 | 456×256 |
| 1920×1080 | 4 | 480×270 |
| 2560×1440 | 5 | 512×288 |
| 3440×1440 | 5 | 688×288 |
| 3840×2160 | 8 | 480×270 |
| MacBook Air 2940×1912 | 7 | 420×274 |
| Celular 390×844 @3 | 3 sobre 780×1688 | 260×563, com borrão de 1,5× |
Correção:
pixelRatio real (buffer = pixels do dispositivo) e controlar o custo do CRT pelos efeitos;image-rendering: pixelated no #c como rede de segurança;camera.ts:184, 503 (55° vertical).round(pxH × aspect) (fx/sprites.ts:74), que pode diferir do lowW real em 1 px: o POW! perto da borda direita pode perder ou duplicar uma coluna de texel.rp.pixelGrid.width para as FX.world/tokyo/index.ts: :340 (focus.clone()), :380 (new Vector3), :381 (2 Matrix4 + Set);audio.ts:195: objeto literal;camera.ts:622: spread;hud.ts:81-98: 5 template strings, além de offsetWidth forçando layout no meio do quadro a cada golpe (:86, 109, 117);player/parkour/index.ts: dezenas de new Vector3/clone por passo durante o parkour (:205-240 detectLedge, :820-870 pendurado).style.transform só quando o valor muda.render.ts:467-475 e :505. O reflexo só liga com chuva, que vem desligada (TUNE.rain = 0).r.render chama scene.updateMatrixWorld() de novo sobre 787 objetos.REFLECT_LAYER na montagem e ao adicionar FX ou props;scene.matrixWorldAutoUpdate = false durante o passe do reflexo.tests/e2e/perf.mjs: só mede tempo de quadro em swiftshader com relógio virtual;main.ts:297-300: o __rubro não expõe o RenderPipeline;renderer.info.autoReset zera a cada um dos 15 render() do quadro;tests/reports/ só existe o baseline da wave 1, gerado de um build de 1,2 MB. A wave 2 (6,6 MB, inimigos novos, máquinas, tecidos) nunca foi medida.rp e usar info.autoReset = false com reset manual por quadro, para registrar draw calls, programas, geometrias e texturas;performance.measureUserAgentSpecificMemory.physics/index.ts:269-289 e :333-337. A posição é gravada 1× por quadro, mas MoveKinematic roda a cada passo.render.ts:273-278);ceil + setViewOffset ampliado deixam o pixel quadrado e alinhado no canto superior esquerdo (render.ts:353-354), e toUv do CRT bate com isso (crt.ts:36-38);?mapa=plato recarrega a página, então não há texturas ou geometrias vazando entre mapas.enemies/look.ts:178-181, enemy.ts:1472-1476);npc/dialog.ts:132/142, ui/pause.ts:278-280, enemies/index.ts:291).fightCenter (enemies/index.ts:94-97), mas os inimigos nascem em x = ±43 (world/tokyo/index.ts:251-252). Vale conferir se o crowd do recast pega esses agentes.19/10/2026. Cópia só de leitura em /tmp/kavo-analise/apps/rubro-arena; nada nela foi editado.
src/camera.ts;src/main.ts;src/input.ts;src/collision.ts;src/world/tokyo/layout.ts;src/player/controller.ts:347-372;src/combat/fighter.ts:288-320 e src/combat/lockon.ts.createWorld('tokyo')), com colisão BVH;Player + Parkour;Input real, com as teclas apertadas por um relógio falso;CameraRig real;Enemies reais (IA XState, fichas de ataque, navmesh recast) e o Fighter real.main.ts: passos fixos, depois a câmera com o tempo real./workspaces/art-and-see-monorepo/.local-state/kavo-analise/agentes/cam-sim/, com harness.ts, run-a.ts, run-bcdfg.ts, run-e.ts, run-lock.ts, run-ladder*.ts e run-sidewalk.ts;cam-sim/out/.cd /tmp/kavo-analise/apps/rubro-arena && npx vite-node <script> -- <cenário>.CAM=patched roda o protótipo da correção (cam-sim/patched-camera.ts).camera.ts:556);A câmera só gira sozinha quando você já está apertando W, e aí ela já está atrás. Toda virada com A ou D deixa o Kavo de lado na tela para sempre. Os atalhos que deveriam consertar isso (Q/E e Shift) giram o Kavo junto, enquanto a tecla de direção está segurada. Na rua de 9 m, a mira travada e as escadas de incêndio somam aproximações bruscas e ângulos quase de cima.
O que você vê: você aperta D para seguir a rua. O Kavo vira e corre de lado na tela, e a câmera fica olhando o perfil dele até você girar com Q/E.
O que medi:
| Teste (sem Q/E) | Câmera girou sozinha | Quadros de perfil | Visão à frente (mediana) | Volta para trás? |
|---|---|---|---|---|
| Segurar D 4 s pela rua (a3) | 0° | 100% | 4,5 m | nunca |
| W 1 s e depois D 3 s (a4) | 0° | 75% | 0,5 m | nunca |
| Na passagem do trem, virar com A para o corredor dos trilhos (b1) | 32° (pelo empurrão de parede, não pelo seguir) | — | 4 m | nunca: ainda 58° fora depois de 3 s |
| Correr com W no meio da rua, câmera atrás (c5) | 0° | 0% | 40 m | já está atrás |
Por quê:
camera.ts:347-359) é multiplicado por sideW. Esse peso só vale 1 quando a tecla aponta para longe da câmera: lateral < 0,3 (camera.ts:306-308, CAM.sideStart/sideEnd = 0,3/0,7 em camera.ts:32-33).camera.ts:311, 354, CAM.recentreAfter) tem a mesma trava.controller.ts:363-371). Enquanto a tecla segue segurada, ela mantém a direção no mundo, mesmo com a câmera girando devagar (< 1,9 rad/s).Prova de que é isto: o protótipo (cam-sim/patched-camera.ts) faz 2 mudanças:
Resultado do protótipo:
| Teste | Hoje | Protótipo |
|---|---|---|
| D 4 s pela rua (a3) | nunca volta; 4,5 m de visão | atrás em 1,03 s; 40 m de visão; o Kavo corre reto, sem círculo |
| Virada no corredor do trem (b1) | nunca; distância cai a 3,6 m | atrás em 0,67 s; distância nunca abaixo de 6,75 m |
| W e depois D (a4) | nunca | atrás em 0,98 s |
O que você vê: você tenta consertar a câmera em movimento, e o Kavo vira para outro lado, roda ou entra na parede.
O que medi:
| Teste | O que aconteceu |
|---|---|
| Correndo de lado com D, segurar E 1,5 s (f3) | A câmera girou 221°, e o Kavo girou 218° junto. Ele continua de lado (−95°), bate na fachada, a câmera chega a 0,18 m e o Kavo sai da tela. |
| Correndo de lado com D, um toque de Shift sem inimigo (f1) | A câmera vai para trás em 0,27 s, e logo depois o Kavo dá meia-volta de 173° e corre para trás. |
| Virou no corredor do trem com A e tocou Shift (b3) | O Kavo volta para a rua. A câmera fica a 0,47 m, com o Kavo fora da tela (y = −1,6). 33% dos quadros ficam abaixo de 3 m. |
| Segurar Shift + D sem inimigo, 3 s (f2) | O Kavo gira 1.406° (quase 4 voltas) num círculo de ~1 m. |
| Jeito que funciona hoje (b5) | Soltar a tecla, segurar E 0,6 s, apertar W: atrás em 0,97 s, e o Kavo freia. São 3 passos em toda esquina. |
Por quê:
controller.ts:366 trata qualquer giro de câmera acima de 1,9 rad/s como manual e gira a direção da tecla junto.camera.ts:338-343) gira 90° em 0,3 s, ≈ 5 rad/s.camera.ts:344-346, ease(dt, 12)) também passa do limite.camera.ts:299).No protótipo (a trava segue só Q/E e mouse):
O que medi: 4–5 inimigos com IA real, 40 s por rodada, 3 sementes por variante.
| Variante | Todos os inimigos na tela | Atacante fora da tela (% dos quadros de ataque) | Ataques que começam fora da tela | Câmera < 3 m | Inclinação máx. | Tempo girando > 30°/s | Giros rápidos a cada 10 s |
|---|---|---|---|---|---|---|---|
| Sem mira, 4 inimigos | 89–97% | 0% | 0 | 0–0,7% | 22° | 0–1,2% | 0–0,4 |
| Sem mira, 5 inimigos | 77–98% | 0% | 0 | 0–5,5% | 21° (1 rodada 81°) | 0–6,8% | 0–1,3 |
| Com mira, 4 | 34–76% | 0–48% | 0–2 | 12–15% (mín. 0,56 m) | 59–71° | 23–31% | 4,5–8,3 |
| Com mira, 5 | 41–79% | 0–48% | 0–3 | 5–17% (mín. 0,38 m) | 57–74° | 17–27% | 3,3–6,6 |
| Com mira + troca Q/E, 5 | 16–24% | 35–47% | 5–6 | 0–7,5% | 36–73° | 24–38% | 4,5–6,8 |
Troca de alvo com inimigos parados (run-lock.ts):
Por quê:
camera.ts:314-334), e gira até 6 rad/s (camera.ts:329).layout.ts:6-14), essa linha quase sempre cruza a calçada e enfia a câmera na fachada. Com alvo, o desvio girando fica desligado (camera.ts:443: canYaw exige !target), e só sobra o guindaste de +0,45 ou +0,9 rad (camera.ts:450). Por isso a câmera fica quase de cima.camera.ts:266-277, 328; fighter.ts:302-308).camera.ts:531, (1 - lb)).brain.ts:117-124, enemies/index.ts:76, tune.ts:30);Nota justa: sem mira, a câmera de combate está boa no teste. O inimigo mais perto fica fora da tela em 0–4% dos quadros, com distância média de 6,9–8,1 m. O enquadramento de ameaças funciona. O estrago está na mira.
Agarra sozinho:
parkour/index.ts:619-621, intent·toAx > 0,6) vale também para quem passa ao lado. O dono disso é o movimento, mas é a câmera que muda de modo.A câmera bombeia:
hang, a distância alvo vai a 13,6 m: 1,4 × 7,6 m (camera.ts:83) mais até 3 m de "enquadrar a próxima beirada" (camera.ts:415).camera.ts:459. O Kavo fica fora da tela 6% do tempo (run-ladder-side.ts).Poste da rota para o telhado da konbini (d4):
layout.ts:6-14). A câmera padrão fica a 7,6 m e 19° (tuning.ts:50-51), ou seja, 7,2 m na horizontal.camera.ts:442-455);run-sidewalk.ts):free.hard (camera.ts:457-459);camera.ts:660-667) e raspam a fachada quando o Kavo anda a 0,7 m dela.camera.ts:451-453).world/tokyo/index.ts:244-246 usa konbini.interior);konbini/index.ts:42);inside, só até o vidro (konbini/index.ts:185).camera.ts:508);camera.ts:504);camera.ts:81);camera.ts:421).| Momento | Como deve ser (números) | Referência |
|---|---|---|
| Explorando a rua |
|
SM64 (Lakitu arrastado); Nesky #4, #22, #23 |
| Calçada estreita (Kavo a < 1 m da fachada) |
|
Cinemachine Collider; Nesky #9, #46 |
| Virando esquina / beco / corredor do trem |
|
Nesky #12 |
| Pulo / triplo |
|
Nesky #28, #45 |
| Parkour: escada, pendurado, tirolesa |
|
Nesky #14; teste run-ladder.ts |
| Giro no poste |
|
Nesky #8, #46 |
| Combate sem mira, vários inimigos |
|
GoW 2018 (Prima), GDC 2019 |
| Mira travada (Shift) |
|
Nesky #31; Ocarina (Z) |
| Troca de alvo (Q/E travado) |
|
Nesky #31; Sifu: corredor de Oldboy em enquadramento lateral |
| Shift sem alvo |
|
Ocarina, Z sem alvo |
| Konbini (interior) |
|
Nesky #16, #42 |
| Diálogo | Ver o item 16 de combate-inimigos-camera.md: plano por cima do ombro, mais alto e mais aberto. |
— |
| Lente (geral) |
|
Nesky #42, #43 |
Em ordem de impacto. Os itens 1 e 2 juntos são o protótipo testado.
camera.ts:347-359):&& sideW > 0 e * sideW;2.3 por 2.6 rad;1.5 * autoK * clamp(|speed|/runSpeed) * min(1, |d|/0.5);CAM.manualHold de 2 para 1.2 (camera.ts:30, 302), com rampa autoK = clamp((manualT-1.2)/0.5);camera.ts:306-312, 354), que vira redundante.manualYawDelta por quadro, só Q/E e mouse (camera.ts:298-299);controller.ts:363-368) por "soma só o manualYawDelta";camera.ts:338-343), Shift segurado (344-346), bigodes (453) e mira (329) nunca entram;camera.ts:314-334, 441-455)::329);lockSide (:270-273) passa a escolher o lado com mais espaço, testando freeAlong dos dois lados;:443, tirar !target);:450);CAM.lockOrbit de 0,5 para 0,45;(1 - lb) zera isso, :531).camera.ts:266-277, 328). Em fighter.ts:302-308, preferir o próximo alvo dentro do cone de ±100° da câmera.camera.ts:442-461, 651-669)::451-453);:458);:459) só pelo raio central e só se a câmera fosse atravessar;:662);:423).camDistance de 7,6 para 6,0 (tuning.ts:50);SHOTS.hang.dist de 1,4 para 0,9 e parkour de 1,2 para 1,0 (camera.ts:82-83);frameExtra de 3 para 1 m (camera.ts:415).pk.kind for poleSwing (camera.ts:347; poleSwing já está em CLIMB, mas a mira e os bigodes ainda giram).this.konbini.inside (world/tokyo/index.ts:244), não interior;SHOTS.interior.fov de 6 para 4 (camera.ts:81).camera.ts:504);camera.ts:421);camera.ts:508).parkour/index.ts:619-621, exigir intent·(−nL) > 0,8);brain.ts:117-124) só depois de 0,5 s de aviso;Rodar os scripts de cam-sim/ (com CAM=orig) e exigir:
| Cenário | Meta |
|---|---|
| a3 / b1 (virada de 90°) | atrás em ≤ 1,2 s; visão à frente ≥ 20 m |
| f1 / f2 / f3 (Shift e Q/E com tecla segurada) | o Kavo vira ≤ 10° a mais do que a tecla pede; nunca gira no lugar |
run-sidewalk.ts |
< 3 m em ≤ 2%; ≤ 1 aproximação brusca a cada 10 s |
run-ladder-side.ts |
nenhuma aproximação maior que 1,5 m em 0,1 s; Kavo sempre na tela |
run-e.ts lock5 e lockswitch5 |
todos na tela ≥ 80%; atacante fora ≤ 10%; inclinação ≤ 45°; giro > 30°/s em ≤ 10% do tempo |
run-lock.ts |
pan por troca ≤ 60° |
| g1 (marquise) | o modo continua default até o vidro |
Data: 19/10/2026. Cópia só de leitura: /tmp/kavo-analise/apps/rubro-arena (caminhos abaixo são relativos a ela). Nada foi editado.
Como conferi
HANDOFF.md, docs/plano-2.0.md, plano-3.0.md, plano-4.0.md, movement-audit.md, wave2.md, wave2-requests/*.md, src/main.ts inteiro e as partes do código citadas em cada linha.npx tsc --noEmit passa limpo..local-state/kavo-analise/agentes/pedidos-scripts/fisica.ts: empurrão, massa, inimigo contra inimigo, hit-stop e inércia. Saída em fisica-saida.md..local-state/kavo-analise/agentes/pedidos-scripts/mundo.ts: monta a rua como o main.ts faz e conta o que o parkour e as interações acham nela. Saída em mundo-saida.md.cd /tmp/kavo-analise/apps/rubro-arena && npx vite-node <script>.combate-inimigos-camera.md), mov #N (movimento-parkour.md), mundo #N (mundo-npc-ui-audio.md) e render #N (render-perf-carregamento.md).Legenda
main.tsPlacar (248 pedidos e promessas)
| Fonte | ✅ | ⚠️ | 🔌 | ❌ |
|---|---|---|---|---|
| A. HANDOFF §2 (44) | 31 | 10 | 2 | 1 |
| B. A conversa e os "o que você recomenda" (66) | 19 | 11 | 2 | 17 |
| C. Planos 2.0, 3.0 e 4.0 + movement-audit (138) | 85 | 30 | 8 | 32 |
| Total | 135 | 51 | 12 | 50 |
Resumo: o que o dono pediu com as próprias palavras (A) está quase todo no jogo, mas quase um em cada três itens tem defeito ou falta. Das recomendações que o Claude prometeu (B e C), quase um terço não existe ou não está ligado. A física de grupo do vídeo, que era o que ele mais queria, é o maior buraco (última seção).
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| Herói original tokusatsu-inseto (Kavo, rig procedural) | ✅ | — | src/player/rig.ts:31-33, main.ts:60-62 |
| Visual pixel low-res 3D (lado curto ~270 px, escala inteira) | ✅ | — | render.ts:14, render.ts:127; render "Verificado e sem problema" |
| TV de tubo anos 80 sutil: scanline, chroma, bleed, grain, bloom, tracking, sem curvar a tela e sem vidro | ✅ | Nunca foi visto numa GPU real (HANDOFF §7.3) | shaders/crt.ts:1-2 ("no curvature, no glass, no tube-frame vignette"), render.ts:15-33 |
| VFX pop art com onomatopeias em pixel (POW!, BAM!, KLANG!...) | ✅ | Efeitos saem em dobro (combate #22) | fx/lettering.ts:4-9 (alfabeto pixel próprio), fx.ts:384 (impact), quadro pop art de 2 tons crt.ts:133, disparado em fighter.ts:932 |
| SFX com cara de anos 80 | ✅ | Ninguém ouviu. O som pode não ligar (mundo #12), o apito não existe e os menus são mudos (mundo #16) | audio/sfx.ts:5, audio.ts:51-53 |
| Trilha synthwave original | ✅ | A trilha troca para a da arena na passagem de nível (mundo #11) | audio/music.ts:8-12, audio/city.ts:8 (108 BPM) |
| SM64: freada | ✅ | Sim: solta o W a 8,6 m/s → run > brake > idle, para em 0,47 s e 2,08 m |
movement/states.ts:98-112, params.ts:40-41 |
| SM64: meia-volta (derrapagem) | ✅ | — | controller.ts:730-733, states.ts:114-127 |
| SM64: pulo triplo (o 3º especial) | ⚠️ | Funciona no chão aberto: cambalhota, faísca, 16,3 m/s. Rente à fachada, o pulo vira wall run (mov #7) e o parkour de chão rouba o Espaço (mov #8, #13). A rua é estreita, então isso acontece muito | controller.ts:851-883 (corrente em :875-881), params.ts:48-51 |
| Pulo duplo no ar (pedido explícito) | ✅ | Apertado 1–3 quadros antes de encostar na parede, ele gasta o wall kick (mov #6) | controller.ts:810-817, controller.ts:885-893 |
| SM64: pulo longo | ✅ | Altura 1,39 m por escolha (mov "Confirmações") | states.ts:59-62, controller.ts:856-860, controller.ts:896-902 |
| SM64: mortal para trás | ✅ | — | states.ts:132, controller.ts:904-909 |
| SM64: mortal lateral | ✅ | Mesma altura do mortal para trás (14,6 m/s) | states.ts:121, controller.ts:911-918, params.ts:57-60 |
| SM64: pisão (ground pound) | ⚠️ | O movimento existe, mas o pisão não acerta nada. O evento groundPound só vai para o áudio, physics.blast nunca é chamado fora de src/physics/, e nem inimigo nem objeto reage. A lista de golpes promete "cai com tudo e quebra o que estiver embaixo". Também há a disputa C→J (mov #22) |
states.ts:70-72, states.ts:207-215, controller.ts:977-983, ui/moves.ts:12; grep: groundPound só em audio.ts:51 |
| SM64: wall kick | ⚠️ | Só sai se o Espaço vier depois do contato. Quem aperta cedo, que é o comum, gasta o pulo duplo (mov #6) | states.ts:65, controller.ts:956-966 |
| SM64: agarrar borda | ⚠️ | Dois sistemas de pendurar brigam, e Espaço no pendurado SM64 faz um "salto de gato" antes de subir (mov #1). A câmera de pendurado não liga nesse caso (mov #17) | controller.ts:544-575, states.ts:232-242 |
| SM64: mergulho e deslize de barriga | 🔌 | Os estados e os clips existem, mas ninguém chama startDive() e o combate wave-2 não tem diveAtk (mov #5) |
controller.ts:938-949, states.ts:160-171, states.ts:201-204 |
| Clean-room (não copiar o n64decomp) | ✅ | Só números de comportamento, com fonte | params.ts:3-7, docs/movement-audit.md |
| Mira travada estilo Ocarina of Time (Shift), troca de alvo | ✅ | Shift esbarra nas Teclas de Aderência (mundo #19) | fighter.ts:289-319, Q/E troca o alvo em fighter.ts:302-305, enquadramento em camera.ts:316-333 |
| Câmeras estilo Zelda64/Mario64, refeita depois de "meio burra" | ⚠️ | Tem Lakitu, controle manual, pontilhado, ameaças e modos. Problemas: antenas tapam a atendente (combate #16), pendurado SM64 sem câmera (mov #17), giro lento do mouse não muda o W (mov #14) e inimigos tremem (combate #17) | camera.ts:100-111, camera.ts:347-355, main.ts:268-277 |
| MvC: lançador | ✅ | — | C+J em fighter.ts:416, moves.data.ts:56 |
| MvC: juggle | ⚠️ | O combo aéreo cai no 3º golpe: dois limites de juggle em escalas diferentes (combate #1) | enemies/tune.ts:26 contra combat/rules.ts:14 |
| MvC: quiques (no chão e na parede) | ✅ | A 2ª cravada depois da Explosão vira knockdown (combate #4) | spike moves.data.ts:62; kiRush e vanish moves.data.ts:65,68; receptor enemy.ts:376-395 |
| MvC: deslize (sliding knockdown, "1 por combo") | 🔌 | O receptor sabe deslizar, mas nenhum golpe do Kavo usa react: 'slide' |
enemy.ts:365-372; moves.data.ts:50-80 não tem 'slide' |
| MvC: OTG | ✅ | Bater no caído sem OTG conta como acerto falso (combate #5) | moves.data.ts:58, fighter.ts:424, enemy.ts:265-269 |
| MvC: super | ✅ | — | J+K em fighter.ts:343-349, moves.data.ts:75 |
| MvC: super encadeado | ✅ | — | J+K de novo: fighter.ts:344 |
| Combos "físicos e impactantes" | ⚠️ | O hitstop, o tremor e o flash existem (seção final). Mas golpe que não conecta dá combo e energia (combate #5), efeitos saem em dobro (#22) e a câmera lenta é global (#10) | fighter.ts:921-944 |
| Segurar para carregar energia (3 níveis) | ✅ | — | fighter.ts:573-574, fighter.ts:795-801, nível 3 = raio em fighter.ts:1450-1465 |
| Bola de energia | ✅ | Atravessa paredes (combate #23). Andando para a frente, K vira investida (combate #24) | kiBall fighter.ts:437 |
| Especial | ✅ | Sai de graça com a barra vazia (combate #7) | kiRush/kiUpper moves.data.ts:65-66 |
| Teclado: 3 botões J/K/L e combinações | ✅ | O Espaço tem ~30 significados (mov #13), e L logo depois de acertar vira Vanish (combate #6) | input.ts:13-29, contracts.ts CHORDS/CHORD_MS |
| Rua de Tóquio | ✅ | — | world/tokyo/index.ts; main.ts:47 (mapa padrão) |
| 7-Eleven real em que dá para entrar | ✅ | — | konbini/brand.ts:31-41, porta e zona interior em main.ts:252-260 |
| ...e comprar | ⚠️ | Só 5 itens, pela atendente. O caixa fica mudo quando ela está perto (mundo #2), café e isotônico não fazem nada (combate #3, mundo #1) e os preços divergem (mundo #4) | npc/brain.ts:10-16, interact/index.ts:50-54 |
| Máquinas de venda: comprar | ✅ | Toca o bipe do caixa (mundo #16), e o prêmio abre um menu no meio da luta (mundo #22) | interact/vending.ts:100-125, roleta ATARI vending.ts:295-315 |
| Máquinas de venda: quebrar | ✅ | Quebrar rende mais que comprar (mundo #20) | vending.ts:150-190; latas como corpos Jolt em interact/debris.ts:47 |
| Máquinas de venda: erguer e arremessar (Shizuo) | ✅ | Depois de assentar, a máquina vira fantasma: não colide mais (mov #11) | J+L em fighter.ts:960-971 → carry.ts; acerta inimigo em carry.ts:160-175 |
| NPC na 7-Eleven para conversar (Sra. Hoshino) | ✅ | Só roteiro (o Claude fica desligado, ver B). A "fala livre" é por palavra-chave (mundo #15) | npc/index.ts, main.ts:128-129 |
| Capa mole nos inimigos | ✅ | Só capitão (da onda 2) e chefe (ondas 4 e 8). A onda 1 não tem capa | enemies/cape.ts, enemy.ts:175, enemy.ts:472-516, enemies/index.ts:121-125 |
| Interação com o ambiente nível parkour de Assassin's Creed | ⚠️ | O módulo é grande (vault, mantle, pendurar, quina, wall run, poste, fio, tirolesa, escada, trampolim). Mas tem conflitos (mov #1, #7, #8, #9, #13, #18), barra e deslizar por baixo não têm onde usar na rua (C3), e o degrau no inimigo não existe (mov #5) | player/parkour/index.ts; mundo-saida.md |
| Link real para jogar em outro navegador | ⚠️ | Só existe um artifact privado do claude.ai, que pede login | HANDOFF §0 e §8 |
| "Não quero que esteja ligado no Claude pra abrir" | ❌ | Nada publicado fora do Claude | HANDOFF §7.1 e §8 |
| Máximo de agentes em paralelo | ✅ | 15 agentes | docs/wave2.md |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| Engenharia reversa do jogo do vídeo (arena top-down de mago com capas Verlet), focando na jogabilidade e física, não na capa | ⚠️ | Virou outro jogo: 3ª pessoa, rua, SM64 e MvC. Dos 5 ingredientes que o Claude listou, 3 vieram bem; o empurrão em grupo, que é o centro do vídeo, não veio (linhas abaixo) | — |
| (1) Empurrão com massa e atrito: o inimigo desliza, não para seco | ⚠️ | Desliza, com atrito exponencial em pé e 11 m/s² no chão. Mas é curto nos golpes comuns (0,3–0,5 m), e não há massa: o empurrão não depende do peso | atrito enemy.ts:693, enemy.ts:892; empurrão sem peso enemy.ts:328, fighter.ts:913; sim §1 |
| (2) Hit-stop | ✅ | hitstop(0,12) congela 8 quadros. Senões: as teclas usam o relógio real (combate #11) e a câmera lenta é global (#10) |
time.ts:9-19, tabela moves.data.ts:47, main.ts:217-223; sim §5 |
| (3) Tremor de tela e flash no golpe | ✅ | Saem em dobro em vanish, parry e Explosão (combate #22) | fx.ts:384- (impact: vítima → câmera → anel → faísca → palavra), camera.ts:186-216 (zoom), enemy.ts:282 + enemies/look.ts:264 (flash branco), render.ts:236-238 (quadro pop art e tranco do CRT) |
| (4) Inimigos que colidem entre si e se empurram em grupo; inimigo jogado nos outros derruba os outros | ❌ | Só o arremesso do agarrão derruba outros (J+L, depois K ou J+L). Lançado, chutado, voando de parede ou nocauteado, o corpo atravessa os outros sem efeito. De pé, eles só se afastam, sem reagir. O "boliche" dos inimigos existe e vem desligado | separação enemies/index.ts:433-447; boliche desligado enemies/tune.ts:17 + index.ts:264; só o arremesso fighter.ts:1043, fighter.ts:1052-1069, combat/config.ts:25; sim §2 e §3 |
| (5) Movimento com inércia curta: responde, mas tem peso | ✅ | 0→8,6 m/s em 0,90 s (arranque rápido até 3 m/s). Solta: freada com derrapagem, 0,47 s e 2,08 m | params.ts:32-44; sim §6 |
| Diferença de massa entre inimigos | ⚠️ | O bruto tem hitstun ×0,6, superarmadura e empurra mais o Kavo. Mas desliza igual ao recruta (0,31 contra 0,33 m) e voa mais longe no golpe de parede (22,3 contra 17,7 m). Entre inimigos, a separação é 50/50 | enemy.ts:62, enemy.ts:194, enemies/index.ts:424, enemies/index.ts:443; sim §1 e §4 |
| O mundo reage aos corpos (cone, lixeira, produto, máquina) | ❌ | Inimigo não é corpo físico. Nenhum golpe aplica impulso em prop. onBodyImpact só quebra as pedras do platô: em Tóquio, world.destructibles = 0. Os props só se mexem empurrados pela cápsula do Kavo |
main.ts:84-94, main.ts:279; grep: nenhum addBody em src/enemies, nenhum applyImpulse de IBody fora de src/physics/; mundo-saida.md |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| Jolt entra e substitui o Rapier: um motor só para tecido, corpos rígidos, ragdoll e veículos | ⚠️ | Entrou só para tecido e props. O ragdoll continua Verlet próprio e não há veículos. Custa 3,47 MB, 54 % do jogo (render #2) | physics/jolt.ts:1, enemies/ragdoll.ts, world/tokyo/index.ts:313-328 |
| ECS (koota ou bitECS) na rua de Tóquio | ❌ | Não está no package.json |
package.json:19-35 |
| @pixiv/three-vrm para NPCs | ❌ | Instalado e nunca importado. A Sra. Hoshino é procedural | package.json:20; render #15; npc/model.ts |
| Theatre.js só no modo dev (direção de câmera) | ❌ | Nem instalado | package.json |
| GSAP (cinemáticas, finisher) | ❌ | Instalado, nunca importado | package.json:23; render #15 |
| troika-three-text (HUD dentro do CRT) | ❌ | Instalado, nunca importado | package.json:32; render #15 |
| Yuka e wawa-vfx (reserva) | ✅ | Reserva, sem gatilho: ok não ter | — |
| recast-navigation (crowd) | ⚠️ | Ligado, mas o navmesh cobre só ±19,6 m em volta do centro da luta. Spawn do Kavo em x=−32 e dos inimigos em x=±43 (nota no fim de render) | enemies/nav.ts:25-38, enemies/index.ts:94-97, layout.ts STREET.fightCenter/fightRadius |
| XState, three.quarks, Tweakpane, stats-gl | ✅ | — | enemies/brain.ts, npc/brain.ts, fx/bursts.ts, tuning.ts, render.ts:247 |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| Capa mole (Jolt soft body, com Verlet de reserva) | ✅ | — | enemies/cape.ts:329-340 |
| 1. A capa infla antes do especial (aviso) | ✅ | Só no investida e no pisão do inimigo | enemy.ts:915-917 |
| 2. Chicoteia na direção do golpe | ✅ | — | enemy.ts:522 |
| 3. Rasga com o dano (a vida fica legível) | ✅ | — | enemy.ts:529-538 |
| 4. Pega fogo com energia; o inimigo entra em pânico | ✅ | Qualquer golpe de energia acende, não só o carregado | enemy.ts:60, enemy.ts:523-527, enemy.ts:1050 |
| 5. Agarrar a capa pelas costas (puxar, girar, arremessar, cobrir a cabeça) | 🔌 | O código existe, mas o startGrab do combate nunca chama (combate #21) |
enemy.ts:545-590, fighter.ts:960-973 |
| 6. A capa indica a patente | ✅ | — | enemy.ts:175, enemy.ts:473-474 |
| 7. Vento do mundo (trem, corredor) em capas, noren, cortinas e varais | ⚠️ | Capas e noren mexem. Os varais são caixas estáticas e não há cortinas. O noren perde as letras (mundo §7 #9) | enemies/index.ts:305-316, world/tokyo/index.ts:378-380, buildings.ts:278-285 |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| 1. Perigos da rua: jogar o inimigo na frente do trem ou do carro | ⚠️ | O trem atropela inimigos e o Kavo, mas o empurrão no Kavo sai invertido (combate #9). Não há carros | enemies/index.ts:331-346, main.ts:233-236, world/tokyo/rail.ts:10-16 |
| 2. Pegar objeto automaticamente perto dele (Yakuza) | ❌ | Precisa de J+L. Só 2 bicicletas e 3 máquinas podem ser erguidas | main.ts:53 (combatOwnsCarry), interact/index.ts:207; mundo-saida.md (pickup = 2 bike) |
| 3. Briga dentro da loja: prateleiras tombam, produtos voam, porta de vidro quebra, atendente se esconde | ⚠️ | A atendente se esconde. As prateleiras são colisão estática. Os produtos (corpos Jolt) só se mexem quando o Kavo passa encostado: golpes e corpos não os movem. A porta não quebra, e inimigos atravessam o vidro (mundo #23) | npc/index.ts:396-407, konbini/interior.ts:66,110,186 (col(...)), world/tokyo/index.ts:328 |
| 4. Pedestres com rotina (Shenmue) | ❌ | — | — |
| 5. Campainha 80s original (não a da FamilyMart) | ✅ | — | audio/world.ts:10, audio/world.ts:168-176 |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
NPC movido a Claude dentro do artifact (sample + db) |
🔌 | O código existe, mas o build publicado usa KAVO_RUNTIME=off. Fora do claude.ai, que é o link que o dono quer, nunca liga (mundo #15) |
npc/runtime.ts:38-47, vite.config.ts:9-10, npc/llm.ts |
| Regras de segurança (estado fora do LLM, propostas validadas, texto do jogador como não confiável) | ✅ | Valem mesmo com o LLM desligado | npc/llm.ts:26-46 |
| Retrato pixel, máquina de escrever, voz "balbucio", Enter livre ou 1–4 | ✅ | O 1–4 usa e.key (mundo #18) |
npc/portrait.ts, npc/dialog.ts:246,259, npc/voice.ts |
| Falas-chave com voz real (TTS "Irasshaimase!") | ❌ | Só balbucio sintetizado e balão de texto | npc/bark.ts:4, fx/palette.ts:88 |
| Falas comuns em cache | ❌ | cache: false (pesa pouco enquanto o LLM está desligado) |
npc/llm.ts:162 |
| 1. Ela reage ao mundo (máquina quebrada, Kavo ferido, onda limpa) | ✅ | — | npc/index.ts:327-360, npc/index.ts:409- |
| 2. Mestra aposentada que ensina golpe secreto | ⚠️ | Ensina a Palma Hoshino, mas o golpe some ao recarregar e não volta (mundo #3) | npc/index.ts:238-239, npc/brain.ts:77,137 |
| 3. Missões: oden ao velhinho, gata no telhado, gangue na porta | ⚠️ | As 3 existem. A gangue não vem na porta e se completa se você morrer (mundo #5), e não há marcador nem relógio (mundo #7) | npc/quests.ts:79-121 |
| 4. Amizade = desconto e itens secretos | ✅ | — | npc/index.ts:151-157, npc/index.ts:213-217 |
| 5. Bater nela chama a polícia | ✅ | Um golpe perto do balcão já conta (mundo #9), e o apito é mudo (mundo #16) | npc/index.ts:364-380 → enemies/index.ts:199-214 |
| 6. Ensina japonês e fofoca do próximo chefe (tutorial disfarçado) | ✅ | Só para quem pergunta | npc/index.ts:147, npc/index.ts:244, npc/script.ts:44-51 |
7. room: ver o Kavo dos amigos na mesma rua |
❌ | — | — |
Detalhes na tabela C4. Resumo: 11 das 13 correções entraram. Falta ligar o mergulho → barriga → rolamento (🔌). A borda tem dois sistemas brigando (⚠️). Do que a auditoria não previu, o parkour criou problemas novos que estragam o SM64 perto de paredes: mov #6, #7, #8 e #13.
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| 1. Degrau no inimigo (renova o pulo duplo) | ❌ | A lista de golpes promete; ninguém zera usedDouble ao pisar num inimigo (mov #5) |
ui/moves.ts:25 |
| 2. Ataque do alto (cair da borda no inimigo = finalização) | ❌ | Só o golpe aéreo comum | grep sem resultado |
| 3. Puxar o inimigo da borda, pendurado | ❌ | — | grep sem resultado |
| 4. Wall run + chute; vault por cima do inimigo (Arkham) | ❌ | O vault só lê geometria | grep sem resultado |
| 5. Máquina de venda como degrau e toldo como trampolim | ✅ | 5 toldos-trampolim, e a máquina leva ao telhado da konbini. A máquina arremessada vira fantasma (mov #11) | buildings.ts:161-165, script.ts:48; mundo-saida.md (springboard 5) |
| 6. Gramática visual discreta (fita amarela nas bordas escaláveis) | ❌ | — | grep sem resultado |
| 7. Desafios de rota integrados às missões | ⚠️ | O oden tem 120 s pelos telhados, mas o tempo não aparece na tela (mundo #7) | npc/quests.ts:109, layout.ts NPC_SPOTS |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| §1 3 botões, com remapeamento salvo | ✅ | — | input.ts:13-44, ui/pause.ts |
| §1 nada de Ctrl/Alt; Espaço não rola a página | ✅ | — | input.ts:38-41, input.ts:118 |
| §1 pausa automática ao perder o foco | ✅ | O menu abre (blur → onPress('pause') → pause.open()). O render #11 diz o contrário; o menu abre, sim. O que segue rodando são o áudio e o trem (render #11) |
input.ts:123-125, main.ts:171 |
| §1 aviso das Teclas de Aderência (5× Shift) | ✅ | Chega tarde, só no 5º toque (mundo #19) | input.ts:143-146, main.ts:141 |
| §1 teste de ghosting na tela de remapear | ✅ | Desenha um QWERTY fixo (mundo #18) | ui/pause.ts:38, ui/pause.ts:247 |
| §2.3 hitstun decay, gravity scaling, damage scaling | ✅ | — | combat/rules.ts:8-13, combat/rules.ts:34-46 |
| §2.3 1 quique no chão, 1 na parede, 1 OTG por combo | ✅ | O deslize não tem golpe (A) | rules.ts:76-84 |
| §2.3 limite de juggle | ⚠️ | Dois limites em escalas diferentes (combate #1) | rules.ts:14, enemies/tune.ts:26 |
| §2.3 janelas de cancel com buffer de 8 quadros | ✅ | A janela de fora roda no relógio real (combate #11) | moves.data.ts:94-103, combat/config.ts:17 |
| §2.3 magnetismo (correção de mira 3D) | ✅ | — | config.ts:9-10, lunge em moves.data.ts:50-58 |
| §2.3 hitstop por golpe + zoom punch | ✅ | — | moves.data.ts:47, camera.ts:186-216 |
| §2.4 guarda com raspão e pushback | ✅ | É infinita (combate #18) | fighter.ts:1228-1240 |
| §2.4 rebate de 6 quadros + câmera lenta + contra garantido | ✅ | L no meio do combo vira Vanish (combate #6) | fighter.ts:1223, config.ts:18 |
| §2.4 esquiva perfeita com câmera lenta | ⚠️ | Nunca dispara contra golpe corpo a corpo (combate #2) | fighter.ts:1224 |
| §2.4 pushblock (frente+L na guarda) | ❌ | — | grep sem resultado |
| §2.5 tabela de golpes, grafo de cancel, Tweakpane | ✅ | O painel congela valores antigos (mundo #13, mov #12) | moves.data.ts, tuning.ts |
| §2.5 editor de hitbox no jogo (TransformControls) | ❌ | — | grep sem resultado |
| §2.5 XState no fluxo do jogo e na IA | ⚠️ | Só na IA e na NPC. O fluxo do jogo é uma variável mode |
enemies/brain.ts, npc/brain.ts, main.ts:144 |
| §3 HUD mínimo (vida, energia em 3 segmentos, combo com dano e escala, mira) | ✅ | Não mostra contra pronto, turbo nem missão (combate #15, mundo #7) | hud.ts:7-23, main.ts:287-290 |
| §3 dicas no contexto ("C+J lança" depois do 3º J) | ❌ | O jogo inteiro tem 3 dicas | main.ts:141, main.ts:153, npc/index.ts:394; o exemplo de ui/index.ts:17 nunca é chamado |
| §3 lista de golpes com mini-demo animado do Kavo | ⚠️ | Só texto, e incompleto (mov #13, mundo #6) | ui/moves.ts:7- |
| §3 tutorial jogável (combo trials) | ❌ | — | — |
| §3 modo treino (input display, frame data, hitbox, boneco, vida infinita) | ❌ | — | — |
| §3 segurar ou alternar; "modo simples" (J repetido) | ✅ | — | ui/pause.ts:37, auto combo fighter.ts:392-397 |
| §3 prefers-reduced-motion nos flashes | ⚠️ | Vale só no HUD e na UI. O quadro pop art, o tranco e o glitch do CRT ignoram | hud.ts:28, ui/dom.ts:88, npc/dialog.ts:57; nada em render.ts nem fx.ts |
| §3 HUD dentro do pipeline CRT (troika ou canvas) | ❌ | O HUD é DOM, sem scanline | hud.ts:41; troika nunca importado |
| §4 replay determinístico por input | ❌ | — | — |
| §4 vitest | ✅ | Só cobre combate e tempo, com inimigo falso | src/combat/*.test.ts |
| §4 stats-gl | ✅ | Entra no pacote mesmo sem ?dev (render #14) |
render.ts:247 |
| §4 Spector.js | ❌ | — | — |
| §4 animação CC0 (Quaternius/Kenney) e Magnific text-to-motion | ❌ | Rede bloqueada; tudo é procedural | HANDOFF §7 roadmap |
| §4 camadas de animação (base + aditiva + IK) | ⚠️ | Camadas procedurais e IK de mão e pé existem. A reação a golpe é um clip inteiro, não uma camada aditiva | animator.ts:60, anim/ik.ts, anim/combat.ts:286 |
| §4 spring bones nas antenas | ✅ | — | rig.ts:14-25, animator.ts:103 |
| §4 animação "em 2s" (12 fps) | 🔌 | Existe, mas vem desligada (twos: false); só o painel ` liga |
animator.ts:14 |
| §4 smear frames | ✅ | — | animator.ts:14, animator.ts:422 |
| §4 Theatre.js | ❌ | — | — |
| §4 three.quarks | ✅ | — | fx/bursts.ts |
| §4 contorno e rim light no pass pixel | ✅ | — | shaders/pixel.ts:2, pixel.ts:29-36, render.ts:38-44 |
| §4 paleta travada + dithering Bayer | 🔌 | Existe e vem desligado (dither: false, paletteLock: false) |
shaders/pixel.ts:6, render.ts:45-48 |
| §4 toon ramp + sombra blob | ✅ | Tem sombra real 2048² em vez de blob (render #16) | shaders/toon.ts |
| §4 decalques (crateras, queimado) | ❌ | Só o texto pintado na rua | world/tokyo/street.ts:55 |
| §4 color script por onda | ❌ | Só o HUD ouve wave |
hud.ts:60 |
| §4 teste de silhueta em escala de cinza | ❌ | — | tests/ |
| §4 camadas por golpe (sample CC0 + cauda synth) | ⚠️ | Só síntese; nenhum sample | audio/sfx.ts:5 |
| §4 escada de tom por golpe do combo | ✅ | — | audio.ts:325-332 |
| §4 sidechain e ducking | ✅ | — | audio.ts:389 |
| §4 vozes (gritos do Kavo; locutor "K.O.!", "COMBO!", "SUPER!") | ❌ | — | grep sem resultado |
| §4 Panner3D | ✅ | O aviso de ataque não usa (combate #13) | audio/voices.ts:7, main.ts:283 |
| §4 bots, regressão visual | ✅ | — | tests/e2e/smoke.mjs, tests/baselines/ |
| §4 orçamento: build reprova acima de 16 ms | ❌ | O teste de desempenho só compara | tests/e2e/perf.mjs:5 |
| §5 #1 combos autorados, ragdoll só no K.O. | ✅ | — | enemy.ts:289-294 |
| §5 #3 animação em 2s como identidade | 🔌 | Desligada (acima) | animator.ts:14 |
| §5 #4 replay; #5 combo trials | ❌ | — | — |
| §5 #6 hitstop variável + zoom punch + flash invertido de 1 quadro | ✅ | O flash é o quadro pop art de 2 tons | crt.ts:133, render.ts:311-315 |
| §5 #8 fichas de ataque (1–2 atacam por vez) | ✅ | A IA trava com ficha presa (combate #12) | enemies/brain.ts, enemies/index.ts:250-252 |
| §5 #9 medidor de estilo que muda a mixagem | ⚠️ | O texto de rank existe (BOM → KAVO!!). A música só tem 2 estágios: vira "intense" com combo ≥10 | combat/moves.ts:4-5, hud.ts:120, audio.ts:345 |
| §5 #10 Vanish | ✅ | Atravessa parede (combate #14, mov #4) | fighter.ts:365-367 |
| §5 #11 beam clash (raio contra raio) | ❌ | — | — |
| §6 3 supers (DHC solo) | ⚠️ | São 2 supers mais a Explosão | moves.data.ts:75-77 |
| §6 finisher em painéis de gibi | ❌ | — | — |
| §6 chefe rival, voo, transformação, crateras, modo foto, gamepad | ❌ | Nenhum getGamepads |
grep sem resultado |
| §6 clima | ⚠️ | A chuva existe e vem desligada, sem som (mundo #21) | world/tokyo/index.ts:33 |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| §0 troca de alvo em Q/E | ✅ | — | fighter.ts:302-305 |
| §0 reaproveitar o RenderPixelatedPass | ✅ | — | shaders/pixel.ts:2 |
| §0 three r186 | ✅ | — | package.json:28 |
| §0 GSAP no lugar do Theatre.js | ❌ | Instalado, nunca usado | render #15 |
| §1.3 segurar ou alternar na mira, guarda e carga | ✅ | — | ui/pause.ts:37, input.ts:230-232 |
| §1.3 pausa no blur, remapear, teste de teclado | ✅ | Ver C1 | — |
| §2.2 rota AUTO: J J J J → lançador + super pulo sozinho | ✅ | Sobe sozinho mesmo cercado (combate #24) | fighter.ts:392-397 |
| §2.2 rota MANUAL até "J+K de novo → J+K+L → repete" | ⚠️ | O combo aéreo cai (combate #1), e a Explosão trava e não devolve os quiques (#4) | — |
| §2.3 hitstun mínimo de 8 quadros, ragdoll só no K.O. | ✅ | — | rules.ts:9, rules.ts:34-37 |
| §2.4 Witch Time | ⚠️ | combate #2 e #10 | — |
| §2.4 dodge offset (segurar J na esquiva mantém o combo) | ✅ | — | fighter.ts:687-688, fighter.ts:1087-1091 |
| §2.4 indicador de golpe iminente | ✅ | Não diz "agora" nem "de onde" (combate #13) | enemies/look.ts:261-264 |
| §2.4 "socar através da sala" (warp até o alvo) | ✅ | — | fighter.ts:425 (warpStrike), config.ts:12 |
| §2.5 golpe no tempo: mais dano, onomatopeia maior, nota extra | ✅ | É sorte, sem sinal na tela (combate #20) | fighter.ts:903-910, fighter.ts:945-948, audio.ts:457-464 |
| §2.5 poses-chave no beat | ❌ | O animador não lê o beat | grep sem resultado em player/ |
| §3.1 as 5 técnicas de hitstop do Sakurai | ✅ | — | fx/shake.ts:55-60 (decai, distância da câmera, ar/chão), hitbox congela em main.ts:217-223 |
| §3.2 empurrão de câmera + shake que decai | ✅ | — | fx.ts:57-61, fx.ts:384 |
| §3.2 recuo de 1–2 quadros do membro que bateu | ❌ | — | grep sem resultado |
| §3.3 hierarquia hitstop → som → câmera → flash → partículas → palavra | ✅ | — | fighter.ts:921-944, fx.ts:384 |
| §4 assets Quaternius, Sonniss, Freesound e Magnific ("disponível agora") | ❌ | Nada gerado nem importado; tudo sintetizado | — |
| §5 mitigar o pixel creep (snap no passe final) | ❌ | — | grep sem resultado em render.ts e shaders/pixel.ts |
| §5 color script por onda; teste de silhueta | ❌ | Ver C1 | — |
| §6 fase 3.1 (controles, pausa, remapear, HUD) | ✅ | — | — |
| §6 fase 3.2 (motor de combo; bot com combo de 30+) | ⚠️ | O bot chegou a 23 golpes (HANDOFF §5.4) | — |
| §6 fase 3.3 (game feel + áudio em camadas Magnific) | ⚠️ | Sem Magnific e sem samples | — |
| §6 fase 3.4 (defesa, tokens com "roubo" fora da tela, bruto e voador) | ⚠️ | Tokens e roubo funcionam (brain.ts:117); defesa com bugs (combate #2, #6, #18) |
— |
| §6 fase 3.5 (camadas aditivas, 2s, smear, poses no beat, movimento CC0) | ⚠️ | 2s desligado; sem poses no beat; sem CC0 | — |
| §6 fase 3.6 (supers, finisher em painéis, quebra de cenário, choque de raios) | ⚠️ | Supers sim. Sem painéis, sem choque de raios, e a quebra de cenário só existe no platô | main.ts:84-94 |
| §6 fase 3.7 (treino, trials, replay VHS) | ❌ | — | — |
| §6 fase 3.8 (chefe rival, voo, transformação, clima, WebGPU, gamepad) | ❌ | Só o r186 entrou | — |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| §3.1 a marca num arquivo só (trocar pela paródia em 1 minuto) | ✅ | — | konbini/brand.ts:31-41 |
| §3.2 postes com fios escaláveis | ✅ | 15 postes viram 30 features (duplicadas, mov #23) e 21 fios | street.ts:111-115; mundo-saida.md |
| §3.2 passagem de nível com sinos, cancela e trem | ✅ | O trem roda no relógio real (mundo #10) | rail.ts:10-16, rail.ts:81-97 |
| §3.2 faixa de pedestre com piyo-piyo | ✅ | O botão não tem função, porque não há carros (mundo "Lacunas") | audio/world.ts:260-270, world/tokyo/index.ts:253 |
| §3.2 escadas de incêndio, ar-condicionado, varais, telhados | ✅ | 8 escadas; varais estáticos | buildings.ts:204-206, buildings.ts:323-329 |
| §3.2 gachapon, bicicletas, cones, lixeiras, noren | ⚠️ | Cones e lixeiras são corpos de física, mas não dá para erguê-los (mundo, tabela HANDOFF §5.9). O noren não tem letras | world/tokyo/index.ts:325-326, street.ts:252-274 |
| §3.2 chuva opcional com poças refletindo | ⚠️ | Começa desligada e sem som (mundo #21) | world/tokyo/index.ts:33, main.ts:261-266 |
| §3.3 interior: bebidas, onigiri e bentô, freezers, estufa, oden, café, ATM, copiadora, revistas | ✅ | É enfeite; só a atendente vende | konbini/interior.ts |
| §3.3 "Irasshaimase!" ao entrar | ✅ | — | npc/index.ts:332-336 |
| §3.3 "esquentar?" para o bentô no caixa | 🔌 | Existe só no caixa, que fica mudo (mundo #2) | interact/items.ts:34-35, items.ts:54 |
| §3.4 inimigos derrubam dinheiro; comida cura | ✅ | — | enemy.ts:616, items.ts:48-57 |
| §3.4 comida "aumenta atributos para sempre" (River City Ransom) | ❌ | Nenhum item dá bônus permanente | items.ts:38-75 |
| §3.4 oden = cura lenta + resistência | ⚠️ | Só a cura lenta | items.ts:50 |
| §3.4 café (energia mais rápida) e isotônico (esquiva) | ⚠️ | Não fazem nada (combate #3) | — |
| §3.4 mangá de técnica desbloqueia golpe | 🔌 | Não dá para comprar (mundo #2), e o golpe some ao recarregar (#3) | items.ts:55-56, moves.data.ts:71 |
| §3.4 gachapon com figurinhas | ✅ | Não rende nada (mundo "ciclo") | interact/gachapon.ts:13-20 |
| §3.4 a lata comprada é objeto físico e pode ser arremessada | ❌ | A lata é só animação na bandeja; o efeito entra na hora | vending.ts:270-283 |
| §3.5 moeda, "clonc", roleta piscando com chance de grátis | ✅ | — | vending.ts:120-125, vending.ts:295-315 |
| §3.5 quebrar: amassa → trinca → estilhaça → destrói, latas e moedas espirram | ✅ | — | vending.ts:150-190, debris.ts:47 |
| §6.2 Espaço = parkour para cima, C = para baixo, A/D pendurado | ✅ | — | parkour/README.md, ui/moves.ts:20-25 |
| §6.2 W contra a parede não sobe sem querer (lição do AC Unity) | ⚠️ | O Espaço ainda segurado da decolagem já dispara wall run (mov #7), e o parkour de chão rouba pulos a 1,9 m (mov #13) | parkour/index.ts:276, parkour/index.ts:306 |
| §6.3 vault (speed e kong) e mantle | ✅ | Atravessam coisas no caminho (mov #18) | parkour/index.ts:362-377 |
| §6.3 pendurar, shimmy, contornar quina, saltar de borda em borda | ✅ | — | parkour/index.ts:850-911 |
| §6.3 wall run vertical | ✅ | — | parkour/index.ts:439-452 |
| §6.3 wall run lateral | ⚠️ | Pega qualquer pulo rente à parede (mov #7) | parkour/index.ts:471-481 |
| §6.3 balanço em barras (semáforo, canos) | 🔌 | O módulo tem; a rua não tem nenhuma tag bar (só a demo) |
parkour/index.ts:566-587; mundo-saida.md; demos/parkour.ts:60 |
| §6.3 equilíbrio no fio | ⚠️ | O Kavo anda até 0,55 m acima do fio (mov #15) | parkour/index.ts:632-637 |
| §6.3 tirolesa descendo os fios | ✅ | 3 tirolesas | street.ts:195; mundo-saida.md |
| §6.3 deslizar por baixo (porta de aço, carro) | 🔌 | O planner existe. Varri a rua inteira (51.684 testes) e há 0 lugares onde ele aceita: as portas de aço vão até o chão e não há carros | parkour/index.ts:488-512, buildings.ts:120-123; mundo-saida.md |
| §6.3 escada de incêndio | ✅ | — | buildings.ts:323-329 |
| §6.3 cano de esgoto | ❌ | Só desenho, sem colisão nem tag | buildings.ts:274-275 |
| §6.3 rolamento ao pousar | ✅ | — | parkour/index.ts:659 |
| §6.3 trampolim no toldo | ✅ | — | buildings.ts:161-165 |
| §6.3 cat leap | ✅ | É ele que rouba o Espaço no pendurado SM64 (mov #1) | parkour/index.ts:429 |
| §6.4 bordas dinâmicas, motion warping + IK, buffer na beirada, momentum | ✅ | — | parkour/index.ts:362-377; controller.ts:869-874; parkour/index.ts:371 |
| §6.4 câmera de parkour (enquadra a próxima borda) | ✅ | Não liga no pendurado SM64 (mov #17) | main.ts:269-276, camera.ts:82-83 |
| §7 fase 4.1 (consertar o movimento SM64) | ✅ | Ver C4 | — |
| §7 fase 4.2 (parkour) | ⚠️ | Ver acima | — |
| §7 fase 4.3 (Jolt + capa com todas as mecânicas) | ⚠️ | Agarrar a capa 🔌 | — |
| §7 fase 4.4 (rua, trem, faixa, chuva, máquinas) | ✅ | Chuva desligada | — |
| §7 fase 4.5 (konbini, ienes, itens, mangás, briga na loja) | ⚠️ | Caixa, mangás e buffs quebrados; briga na loja pela metade | — |
| §7 fase 4.6 (NPC com Claude, roteiro, missões, memória) | ⚠️ | Claude desligado; memória só no localStorage | — |
§7 fase 4.7 (pedestres com ECS + recast + VRM, perigos de rua, presença room) |
❌ | Só o trem | — |
| Pedido | Status | O que falta / está errado | Evidência (file:line) |
|---|---|---|---|
| 1. Buffer de pulo de 150 ms; perto do chão, Espaço vai para a corrente do triplo | ✅ | Perto da parede o duplo ainda come o wall kick (mov #6) | params.ts:52-56, controller.ts:810-817 |
| 2. Pouso pesado só acima de 9 m e nunca em triplo, mortal, chute ou pulo longo; o triplo pousa correndo | ✅ | — | controller.ts:46, controller.ts:1008-1016, params.ts:81 |
| 3. Impulsos 9,9+0,25·v / 12,3+0,25·v / 16,3 / 14,6 / pulo longo 7,1 ×1,5 | ✅ | Gravidade do pulo longo 0,7, por escolha | params.ts:46-66 |
| 4. Gravidade 28,4 simétrica, terminal 17,7, sem flutuar, corte ×0,25 | ✅ | Os golpes de combate mantêm 30/46 | params.ts:73-76, states.ts:79, params.ts:100-102 |
| 5. No ar o corpo não gira; a direção só empurra | ✅ | A deriva lateral liga e desliga de uma vez (mov #25) | controller.ts:791-801 |
| 6. Mergulho → deslize de barriga → rolamento ligados de verdade | 🔌 | Inalcançável (mov #5) | controller.ts:938 |
| 7. Combate como camada: o golpe guarda o estado do pulo e devolve | ✅ | — | controller.ts:241-262 |
| 8. Velocidade máxima em 0,8–1,0 s, sem sprint, estado de freada com poeira | ✅ | Sim: 0,90 s. O animador ainda tem sprint 11,5 fixo (mov #20) |
params.ts:34-42, states.ts:98-112 |
9. Uma regra só de meia-volta; o turn mantém velocidade |
✅ | — | controller.ts:730-733, params.ts:44 |
| 10. Beirada de 0,25 a 1,2 m caindo; probe de pouso ≤0,3 m | ⚠️ | Os números estão certos, mas o parkour pendura de 1,3 a 2,3 m por cima disso, com dois sistemas (mov #1). O teste e2e falha por causa do harness (mov #3) | params.ts:19, params.ts:83-84 |
| 11. Rampas (escorrega abaixo de 38°, sobe mais devagar, desce mais rápido) e degrau suavizado em 3 quadros | ⚠️ | Implementado; contra a base de uma rampa íngreme ele anima corrida parado no lugar (mov #10) | controller.ts:602-611, controller.ts:738-746 |
| 12. Câmera gira no máximo 0,5 rad/s e não gira andando de lado | ✅ | — | camera.ts:31, camera.ts:347-355 |
| 13. Mortal lateral da mesma altura do mortal para trás | ✅ | — | params.ts:57-60 |
| Arquitetura: máquina de estados explícita, um buffer de intenção, combate por cima, um filtro de teclado, eventos físicos para o animador, altura de pico | ✅ | — | states.ts:5-10, controller.ts:84, controller.ts:328 |
| Extra: teclado vira analógico (rampa de 0,12 s) | ✅ | — | input.ts:45-48 |
| Extra: squash e stretch, poeira proporcional à queda, cambalhota no triplo | ✅ | — | animator.ts:14, controller.ts:1022 |
| Extra: som no topo do triplo | ❌ | O evento apex existe e ninguém toca som nele |
controller.ts:328; nada em audio* |
| Extra: tolerância de 3 quadros para o C do pulo longo | ✅ | — | states.ts:59-62, params.ts:66 |
| Extra: bonk suave na parede | ✅ | — | controller.ts:700-705 |
| Extra: câmera Lakitu; sem Alt para andar devagar | ✅ | — | camera.ts:349-355 |
Prometido e ligado a nada (🔌)
controller.ts:938).enemy.ts:545-590).interact/index.ts:50-54).enemy.ts:365-372).animator.ts:14, render.ts:45-48). A "identidade visual única" do plano-2.0 §5 #3 não aparece para quem joga.enemies/tune.ts:17).Prometido e não feito (❌)
room, pegar objeto automaticamente, a lata comprada como objeto, comida com bônus permanente.Prometido, feito e quebrado (⚠️ com bug nos outros relatórios)
Novo neste relatório (ainda não estava nos outros)
physics.blast nunca é chamado.onBodyImpact não tem o que quebrar.Ele disse: "a capa não me importa muito, o que eu mais gostei foi da JOGABILIDADE e FÍSICA". A resposta prometeu 5 ingredientes. Medido com as classes reais (pedidos-scripts/fisica-saida.md):
| # | Ingrediente prometido | Veredito | O que a medida mostra |
|---|---|---|---|
| 1 | Empurrão com massa e atrito (desliza, não para seco) | ⚠️ | Desliza, mas curto e sem peso. Golpes comuns: 0,31–0,48 m em ~0,4 s, com atrito exponencial (enemy.ts:693). Golpes fortes: 4,9 m (voadora), 8,4 m (arremesso), 17,7 m (investida). O empurrão não divide pelo peso (enemy.ts:328): recruta 0,33 m contra bruto 0,31 m num jab, e o bruto voa mais longe na investida (22,3 contra 17,7 m) |
| 2 | Hit-stop | ✅ | 0,05–0,2 s por golpe, com tabela do Sakurai (moves.data.ts:47): 0,12 s = 8 quadros congelados. Senões: teclas no relógio real (combate #11), câmera lenta global (#10) |
| 3 | Tremor de tela e flash | ✅ | Tremor da vítima (ar/chão, decai, escala com a distância), empurrão e zoom da câmera, flash branco no inimigo, quadro pop art de 2 tons e tranco do CRT. Problema: tudo sai em dobro em vanish, parry e Explosão, e a câmera satura num super (combate #22) |
| 4 | Inimigos que colidem e se empurram em grupo; um inimigo jogado derruba os outros | ❌ | É o ingrediente central do vídeo e não veio. De pé, eles só se afastam sem reagir (B deslocado 0,16–0,21 m). Lançado, voadora, investida e nocauteado passam através do vizinho (menor distância 0,01–0,08 m, nenhum dano, nenhum estado). Só o arremesso do agarrão (J+L e depois K) derruba outros, pelo código do combate (fighter.ts:1052-1069). O boliche dos próprios inimigos vem desligado (tune.ts:17). E nenhum corpo mexe em cone, lixeira ou produto |
| 5 | Movimento com inércia curta: responde, mas tem peso | ✅ | 0 → 3 m/s quase na hora, 8,6 m/s em 0,90 s. Ao soltar: freada com derrapagem e poeira, 0,47 s e 2,08 m. Sem giro no ar e sem pouso pesado nos pulos bons. O que estraga o feel não está aqui: está nos conflitos com o parkour perto de paredes (mov #6, #7, #8, #13) |
Em uma frase: hit-stop, tremor e inércia estão bons. A física de grupo que fez o dono gostar do vídeo não existe: empurrão sem peso, corpos que atravessam uns aos outros e um mundo que não reage aos golpes. Para chegar lá:
bowling() já existe em enemies/index.ts:349-385.kb e o deslize pelo peso.character, ou IBody.applyImpulse no golpe).physics.blast no pisão e nas explosões.Resposta curta: são cinco defeitos de código, não um problema de "sensação". Em ordem de impacto:
Um sexto detalhe transforma os casos 2 e 5 num "atravessar o prédio inteiro": a colisão não empurra para fora quem já está dentro (collision.ts:201).
Método:
createWorld('tokyo'), as máquinas de venda, Collision, Player, Parkour, Input e CameraRig. Nada foi editado em /tmp/kavo-analise.Legenda: 🔴 quebra o jogo · 🟠 atrapalha muito · 🟡 polimento.
O que já estava em movimento-parkour.md (itens 1, 4, 7, 8, 9, 11, 13, 16, 18, 19, 23) e camera.md (item 4) não é repetido aqui. Esses itens só aparecem quando os números novos mudam a gravidade deles.
| Detector | Definição |
|---|---|
| PRESO | Uma direção (W/A/S/D) segurada ≥ 0,5 s e os pés andam < 5 cm, sem estar empurrando uma parede de frente. Pendurado com W conta, porque o jogador quer subir. |
| Empurrando parede | Mesma coisa, mas com o corpo encostado de frente numa parede. É o normal de qualquer jogo; aparece aqui só para separar dos casos acima. |
| Não sai andando | No fim de cada roteiro: solta as teclas e tenta andar 0,5 s em cada uma das 4 direções. Conta quando nenhuma move ≥ 0,4 m (antes de tentar Espaço ou C). |
| DENTRO | O centro da cápsula fica ≥ 4 cm dentro de uma caixa de colisão (teste exato de ponto contra caixa). Entre parênteses, quando fica ≥ 0,36 m dentro, ou seja, o corpo inteiro. |
| Encaixado | A cápsula de parede fica > 12 cm dentro de algo, fora de um movimento de parkour. |
| Fresta | O corpo fica dentro de uma das frestas entre prédios, abaixo do telhado mais baixo. |
| LIMBO | Um movimento de parkour dura > 3 s com < 0,3 m de progresso e tecla apertada, ou o estado alterna ≥ 6 vezes em 1 s. |
| FALHA AO SUBIR | O jogador tenta subir algo alcançável e não fica de pé em cima em 2,5 s. Vale para beirais ≤ 2,3 m (ou passar por cima, se o obstáculo tiver ≤ 1 m de fundo), escadas, telhado vizinho mais alto e "pendurado → subir". |
| Elemento | Tentativas | PRESO | Empurrando parede | Não sai andando | DENTRO (corpo todo) | Encaixado | Fresta | LIMBO | FALHA AO SUBIR |
|---|---|---|---|---|---|---|---|---|---|
| Fachadas (22 prédios + konbini) | 864 | 4,6% | 56,3% | 1,9% | 15,6% (4,2%) | 8,2% | 2,7% | 1,9% | – |
| Beirais baixos ≤ 2,3 m (AC, caixa-d'água, degraus, máquinas, cerca) | 1.709 | 8,9% | 13,3% | 1,8% | 3,4% (1,7%) | 10,6% | 7,7% | 4,0% | 32,9% (556/1.688) |
| Postes finos ≤ 2,3 m (sem topo para ficar) | 67 | 1,5% | 46,3% | 0% | 11,9% (6,0%) | 34,3% | 3,0% | 4,5% | – |
| Escada de incêndio (subir) | 120 | 29,2% | 26,7% | 0% | 13,3% (3,3%) | 29,2% | 3,3% | 3,3% | 52,5% (63/120) |
| Escada (passar correndo ao lado) | 79 | 0% | 8,9% | 0% | 17,7% (0%) | 0% | 0% | 0% | – |
| Postes de luz (15) | 883 | 3,4% | 44,2% | 0,6% | 54,2% (3,1%) | 4,8% | 3,2% | 0,8% | – |
| Tirolesas (3) | 108 | 7,4% | 86,1% | 0,9% | 7,4% (7,4%) | 8,3% | 0% | 7,4% | – |
| Fios | 43 | 53,5% | 27,9% | 0% | 2,3% (2,3%) | 2,3% | 0% | 30,2% | – |
| Telhado → telhado vizinho | 598 | 23,4% | 67,2% | 0% | 0,8% (0,8%) | 22,6% | 22,6% | 6,0% | 86,5% (257/297, só subidas) |
| Descer do telhado para a rua | 384 | 12,2% | 78,1% | 4,2% | 3,4% (1,0%) | 4,2% | 0,5% | 6,0% | – |
| Pendurado (parapeito / sacada) → subir | 400 | 70,8% | 0% | 2,3% | 16,0% (0%) | 0% | 10,0% | 37,5% | 100% (222/222) |
| Corrida na parede | 516 | 0,4% | 34,9% | 1,2% | 18,2% (1,4%) | 6,2% | 1,0% | 2,9% | – |
| Vanish / blink perto da parede | 459 | 0% | 0,9% | 0% | 71,9% (39,4%) | 73,2% | 0% | 0% | – |
| Total | 6.230 | 12,2% | 34,8% | 1,3% | 19,7% (4,9%) | 14,1% | 5,9% | 5,5% | 47,2% (1.098/2.327) |
Resumo geral:
pkFailed = false nas 6.230), então o item #23 do movimento não é a causa do "preso".CAM=fixed) e os números não mudaram: dentro 15,7% contra 15,6%, pendurado 8,3% contra 8,1%. O problema é movimento e colisão, não câmera.player.pos (physics/index.ts:269-289, 333-337) e nunca devolve nada ao player.Quem põe o Kavo dentro do prédio (estado no primeiro quadro dentro, em todas as tentativas):
| Causa | Tentativas | Corpo todo dentro |
|---|---|---|
| Giro no poste | 681 | 58 |
| Vanish/blink | 330 | 181 |
| Escada | 73 | 0 |
Volta na quina (cornerWrap) |
60 | 8 |
Pendurado SM64 (ledgeHang) |
20 | 19 |
| Tirolesa | 13 | 11 |
Subida SM64 (ledgeClimb) |
10 | 8 |
Corrida para cima na parede (wallRunUp) |
9 | 4 |
Salto entre bordas (ledgeJump) |
8 | 6 |
standOk = true, como a fachada da konbini e a caixa-d'água.parkour/index.ts:233-243: a "espessura" do topo é medida só até o topo mudar de altura. Ela precisa ser ≥ 0,5 m, e o lugar onde o Kavo ficaria em pé (stand) é um ponto ≤ 0,45 m atrás da borda.world/tokyo/buildings.ts:107), com o telhado 0,4 m abaixo dele. A espessura medida dá 0,33, então standOk = false.standOk (parkour/index.ts:810). Sem ela, procura um salto para cima (findLedgeJump('up')), não acha e não faz nada, sem nenhum aviso.stepHang só usa a frente da tecla para o salto para trás (parkour/index.ts:788-805).npx vite-node <pasta>/repro.ts -- parapetstandOk false).layout.ts:23, poleZ). As fachadas começam em |z| = 4,5 (layout.ts:14, curbHalf). Isso é 0,3 m de distância.parkour/index.ts:610, 1014), sem colisão (controller.ts:466). Então o centro passa 0,2 m dentro da parede e o corpo, 0,55 m.parkour/index.ts:1020-1023) é um pulo na direção da tangente, alinhada com a tecla. Com W apontando para o prédio, o Kavo sai para dentro.movimento-parkour.md #9). Agora a gravidade sobe para 🔴.repro.ts -- pole2wallSlide), metade dentro de cada uma.world/tokyo/index.ts:48-75: quase todos os vizinhos têm 0,4–0,5 m de fresta. São 17 frestas, e mais duas de 0,4 m em volta da konbini.params.ts:10, RAD 0,35) e não cabe. O empurrão das paredes é só horizontal e tem 2 passadas (controller.ts:670-690, collision.ts:191), então as duas paredes se anulam e nada segura o Kavo na vertical.buildings.ts:107), menos que o degrau automático de 0,45 m (params.ts:15, STEP_H). Andar contra ele sobe nele sozinho, e o passo seguinte é o vazio da fresta.linkRoofs só põe degraus no telhado mais baixo quando a diferença é ≥ 1,5 m (buildings.ts:347, index.ts:260-270) e nunca fecha a fresta.repro.ts -- gapO que o jogador vê:
Números (chegou ao telhado, por ângulo de chegada):
| Ângulo | Chegou ao telhado | Preso no topo |
|---|---|---|
| 0° | 14 de 16 | – |
| ±30° | 32 de 32 | – |
| ±60° | 1 de 32 | 31 |
| Qualquer ângulo + Espaço no meio da subida | 10 de 40 | – (jogado de costas) |
camera.md #4 com números.Causa:
parkour/index.ts:621-623), e não a normal da escada que o prédio já grava (buildings.ts:325, normal: outward).parkour/index.ts:1126) cai fora do telhado. Então a altura fica presa em topo − 1 m (parkour/index.ts:1138).parkour/index.ts:1114-1118).Repro: repro.ts -- ladder60
ladder com os pés em 12,4 para sempre (telhado 12,4, topo da escada 13,4).O que o jogador vê: inimigo prensado na parede, depois o vanish. A câmera e o Kavo aparecem dentro do prédio.
Números:
| Distância inimigo→parede | Dentro | Corpo todo dentro |
|---|---|---|
| 0,25 m | 96% (87/91) | 96% |
| 0,4 m | 96% | 73% |
| 0,6 m | 96% | 29% |
| 0,8 m | 63% | 0% |
| 1,0 m | 10% | 0% |
Causa: fighter.ts:1133-1142 põe o destino 1 m atrás do alvo e fighter.ts:1151-1160 chama place() sem nenhum teste.
Repro: repro.ts -- vanish
collision.ts:201 ignora faces vistas por trás.collision.ts:152). Lá dentro não existe chão até o chão da rua, que passa por baixo de todos os prédios (street.ts:52). Então o Kavo pousa em y = 0 dentro do prédio.place(), testar se o ponto está dentro de algum sólido e empurrar para fora pela menor distância. Ou impedir o movimento antes (causa 2).buildings.ts:333-342):standOk false).repro.ts -- acstack: 0 de 6 em cima.repro.ts -- vendinglayout.ts:47-53) mandam o jogador justamente por essas rotas: o poste com degraus em x −6,6 (causa 2), a tirolesa, as máquinas de venda e a pilha de AC em x −12,6.Das 556 falhas ao subir beirais ≤ 2,3 m, 253 (46%) não tiveram nenhum movimento de parkour: o Espaço virou pulo comum.
Taxa de falha por jeito de apertar:
| Jeito | Falha |
|---|---|
| Toque | 26% |
| Duplo | 30% |
| Segurado 0,4 s | 46% |
| Repetido | 27% |
Segurado falha mais porque também liga a corrida lateral na parede (#7 do movimento).
parkour/index.ts:276, alcance 0,85 + 0,12·v). Não há antecipação: o mesmo aperto, 1 quadro depois, já virou pulo do SM64 (controller.ts:297 e states.ts:41).states.ts:79, jumpCut), o que dá ~1,1 m.Números (chegou à rua):
| Roteiro | Chegou à rua | Observação |
|---|---|---|
| Só W | 84 de 96 | |
| W+toque | 58 de 96 | 12 pousaram no fio e ficaram parados (LIMBO) |
| W+Espaço segurado | 29 de 96 | 25 terminaram pendurados (sacada ou parapeito da frente; a corrida + pulo atravessa os 9 m de rua) |
Causa: o pendurado no ar pega qualquer parede à frente quando a tecla aponta para ela (parkour/index.ts:311, 321-324). No fio, só a tecla ao longo do fio anda (parkour/index.ts:1046); W perpendicular fica parado (30% de LIMBO nos fios).
cornerWrap); 44 deles passam 4–12 cm para dentro da fachada.parkour/index.ts:845-882 faz um arco sem colisão.detectLedge passa a "achar beiradas" pelas faces internas das lajes coladas na fachada: a plataforma da escada e a sacada.controller.ts:570-573 sempre pendura 2,12 m abaixo do topo, sem o teste de chão que o parkour tem (parkour/index.ts:322-326).parkour/index.ts:646-647).vy e deslize (controller.ts:460).controller.ts:466-486).parkour/index.ts:690-703).parkour/index.ts:370, 395-398).Velocidade antes → depois, e quanto das transições perde ou ganha mais de 4 m/s num quadro:
| Transição | Velocidade | Δv > 4 m/s num quadro |
|---|---|---|
| Corrida → escada | 7,7 → 1,3 m/s | 97% |
| Pulo → pendurado | 5,3 → 0,9 | 84% |
| Queda → tirolesa | 8,3 → 2,4 | 97% |
| Corrida → vault | 7,0 → 5,7, mudando de direção | 100% |
| Corrida → toldo | 7,5 → 0 | 100% |
Subir de pendurado e sair do topo da escada termina em 0 m/s (parkour/index.ts:810, velocidade 0).
A corrida para cima na parede sem beirada sempre termina num chute para trás de 4 m/s (parkour/index.ts:454).
Um corpo sob gravidade muda no máximo 0,47 m/s por quadro (28,4 m/s² a 60 Hz).
Pior salto de velocidade num quadro, em média:
| Movimento | Média | Máximo |
|---|---|---|
Subir (mantle) |
3,0 m/s | – |
| Giro no poste | 5,9 m/s | – |
| Corrida para cima na parede | 8,5 m/s | – |
| Corrida lateral na parede | 18,6 m/s | 40,8 m/s |
Na tela isso aparece como encaixes e "teleportes" curtos: o corpo é puxado para a parede, o pendurado encaixa em 0,22 s (parkour/index.ts:324).
anim/parkour.ts) tocado pelo progresso do trilho.anim/parkour.ts:245-256); o pé só gruda pendurado ou na escada.parkour/index.ts:7-11), mas aqui o "warp" é o trilho sem colisão. É ele que causa as causas 2, 6 e 10.states.ts:238).Pasta: /workspaces/art-and-see-monorepo/.local-state/kavo-analise/agentes/parkour-sim/. Rodar sempre a partir de /tmp/kavo-analise/apps/rubro-arena, com npx vite-node <pasta>/<arquivo>.ts.
| Arquivo | O que faz |
|---|---|
harness.ts |
Mundo real, Collision, Player, Parkour, Input em relógio falso, CameraRig real e máquinas de venda (como main.ts:55). Grava cada caixa de colisão (kit.Solids.box, konbini, máquinas) para o teste exato de "dentro". |
lib.ts |
Detectores: preso, dentro, encaixado, fresta, LIMBO, teste de fuga em 4 direções, teste "pendurado → W → W+Espaço" e transições de velocidade. |
attempt.ts |
Executa uma tentativa: valida o ponto de partida (nunca começa dentro de nada), roda o roteiro, aplica os detectores, faz a fuga e grava um registro JSON. |
run-facade.ts |
23 fachadas × pontos com semente × andando/correndo × 6 roteiros (W, Espaço 1×/2×/3×, segurado, pulo longo) × ângulo 0/±30/±60°. CAM=fixed repete com a câmera parada. |
run-ledges.ts |
Mapa de alturas a cada 0,25 m (rua e telhados) → 178 grupos de beirais de 0,5–2,3 m × 8 jeitos de chegar. |
run-elements.ts |
Escadas, postes, tirolesas, fios, telhado→telhado, descer, pendurado+quina, corrida na parede e vanish. SWEEP=pole, DUMP=<id>, SUF=-x. |
report-data.cjs, report-extra.cjs, agg.cjs |
Geram as tabelas deste relatório a partir de out/*.jsonl. |
repro.ts |
Repros mínimos, rodados com repro.ts -- <nome>: parapet, pole2, gap, ladder60, vanish, acstack, vending. |
Limites:
main.ts, e o toque de Espaço dura 100 ms.startAction + place() como fighter.ts faz.Data da pesquisa: 19/10/2026.
Regras deste arquivo:
Como os jogos bons fazem
O que costuma dar errado
Não achei: fonte primária sobre o onboarding de Hades ou A Hat in Time (só achei material sobre Early Access de Hades). Também não abri nenhum vídeo do GMTK.
O que isso significa para o Kavo: nos primeiros 60 segundos, um verbo por vez num espaço que obriga a usá-lo (andar → pular → J → pulo duplo → K → L → Z), na ordem de 4 tempos do Mario. A lista completa de golpes fica no pause e, se houver tela de carga, ela vira treino de combo como em Bayonetta. Um texto explicando tudo de uma vez não segue esse padrão.
Receitas concretas
O perigo de exagerar
Não achei: estudo ou pós-morte sobre onomatopeia na tela atrapalhando a leitura. Também não abri a palestra original "The Art of Screenshake" nem o vídeo de hitstop do Sakurai (só a coluna traduzida).
O que isso significa para o Kavo: o hitstop deve crescer com o dano e o golpe precisa de som coerente com o impacto. No máximo 2 inimigos atacam por vez, com seta na borda para quem está fora da tela. Shake, CRT e onomatopeia ficam no nível "médio", ligados ao acerto do jogador, e cada um ganha um slider próprio. Os estudos mostram que o extremo piora o jogo.
Exemplo de muitos sistemas e pouca profundidade: Biomutant (2021)
Sobre IA e a sensação de progresso
Não achei: pós-morte confiável de um jogo indie solo morto por excesso de features. Os resultados de busca eram blogs genéricos (wayline.io), que descartei.
O que isso significa para o Kavo: o loop de 30 segundos é "onda de inimigos → combo J/K/L com pulo duplo → arremessar máquina". Ele deve ficar excelente isolado (uma arena cinza, como o FreeFlow) antes de mais missões ou mais 7-Eleven, e a variedade vem de mudar o contexto, não de empilhar sistemas. Com 15 agentes em paralelo, o risco é o do Biomutant (muitos sistemas, lock-on frouxo, pouco feedback). A METR mostra que a sensação de "andou rápido" com IA pode enganar.
A técnica do t3ssel8r explicada por quem a reproduziu (Godot):
David Holland, 24/09/2024 — https://www.davidhol.land/articles/3d-pixel-art-rendering/
Esse truque só funciona bem com câmera ortográfica. Na ortográfica, todo pixel se desloca igual e um único encaixe corrige todos. Na perspectiva, "pixels em profundidades diferentes andam em ritmos diferentes, e nenhum encaixe único corrige todas as profundidades". O artigo propõe o "texel splatting" (cubemap a partir de um ponto fixo), que ainda tem limitações em aberto. Dylan Ebert, arXiv, 15/03/2026 — https://arxiv.org/abs/2603.14587
O plugin UPixelator (Unity) confirma: "só a câmera ortográfica tem o benefício da redução de pixel creep". Objetos que se movem tremem, a menos que também sejam encaixados na grade. https://github.com/Radivarig/UPixelator_Documentation
A Short Hike (jogo lançado, feito quase sozinho): a ideia era usar "o menor número de pixels possível", com sombreamento chapado, sem anti-aliasing e com contorno suave para a leitura. Como alguns jogadores não gostaram, o dev adicionou uma opção para ajustar o tamanho do pixel. Adam Robinson-Yu, PlayStation Blog, 05/08/2021 — https://blog.playstation.com/2021/08/05/crafting-a-tiny-open-world-a-look-behind-the-scenes-at-the-creation-of-a-short-hike/
Contorno em tudo destrói a leitura. Um vídeo técnico sobre contornos em 3D pixel-art usa máscara por camada porque contornar todos os objetos "destrói a legibilidade". A silhueta vem da profundidade e o vinco das normais. Transcrição da série "Pixel Perfect" (autor não identificado na página) — https://sozai.app/transcript/drawing-the-line-on-3d-pixel-art-pixel-perfect-transcript/index.md
Contraexemplo, resolução baixa sem cuidado: Lethal Company roda a 860×520 internos. Um patch da comunidade para epilepsia existe para reduzir o "flicker de textura e geometria causado pela resolução interna muito baixa". https://thunderstore.io/c/lethal-company/p/GFG/EpilepsyPatch/
Paleta: o portfólio 2025 de Bruno Simon tira as cores de uma textura de paleta compartilhada, não de vertex colors. Awwwards — https://www.awwwards.com/brunos-portfolio-case-study.html
Não achei: fonte confirmando que Lone Ruin tem visual pixel. A página da Steam o marca como "2.5D, Isometric, Stylized" (https://store.steampowered.com/app/1452070/). Unrailed e Cassette Beasts não foram verificados e ficaram de fora. Também não abri os vídeos do próprio t3ssel8r.
O que isso significa para o Kavo: com ~270 px no lado curto e câmera de ação em perspectiva, encaixar a câmera na grade não elimina o tremido como no t3ssel8r, que usa câmera ortográfica. Para isso, os caminhos são câmera ortográfica (ou, por inferência minha, perspectiva com FOV bem baixo, que se aproxima dela) mais encaixe e deslocamento de subpixel. Fora isso, contorno só em personagens e objetos interativos, paleta fechada e um slider de tamanho de pixel como em A Short Hike. Antes de mexer, vale um teste de flicker numa panorâmica lenta.
Tamanho e espera
Como os bons são montados
package.json usa three + @dimforge/rapier3d + @gltf-transform. https://raw.githubusercontent.com/brunosimon/folio-2025/main/package.jsonSuporte e hardware
powerPreference: "high-performance" (dica para notebooks com duas GPUs) e failIfMajorPerformanceCaveat. MDN — https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContextrequestAnimationFrame segue a taxa do monitor (60, 75, 120 e 144 Hz são comuns). Se o movimento não usar o tempo decorrido, "a animação roda mais rápido em telas de alta taxa". MDN — https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrameFísica em wasm (custo de download)
rapier_wasm3d_bg.wasm com 3.082.103 bytes brutos, ~1,13 MB em brotli. https://cdn.jsdelivr.net/npm/@dimforge/rapier3d-compat@0.21.0/dist/rapier_wasm3d_bg.wasmjolt-physics.wasm.wasm com 2.021.569 bytes brutos, ~0,69 MB em brotli. https://cdn.jsdelivr.net/npm/jolt-physics@1.1.0/dist/jolt-physics.wasm.wasmthree.module.js 0.186.1 tem 662.772 bytes brutos.Não achei: confirmação de que o Krunker usa three.js (a página da Wikipedia deu 404 e as outras fontes eram fracas). Também não achei benchmark Rapier × Jolt no navegador.
O que isso significa para o Kavo: os 6,6 MB ficam abaixo dos 10 MB onde a Poki mediu perda de ~20% nos EUA. Mesmo assim, o jogo deve abrir jogável em menos de 10 s, com o mínimo primeiro e o resto depois. Movimento e física precisam usar dt real ou passo fixo (MDN, Fiedler). Inferências: se algo andar por frame, em 144 Hz fica 144/60 = 2,4× mais rápido. A ~270 px de altura, o preenchimento de pixels deixa de ser gargalo até em GPU integrada, e o custo passa a ser CPU, física e draw calls. Se o Kavo embute física wasm, ela sozinha pode responder por 0,7–1,1 MB comprimidos, e embutida como base64 cresce ~33%. Vale medir o que ocupa os 6,6 MB.
KeyboardEvent.code representa a tecla física, "não alterada pelo layout". É o recomendado para jogos: no AZERTY, o WASD físico continua funcionando. MDN — https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/codeNão achei: evidência boa sobre jogos de luta tipo plataforma (Rivals, Brawlhalla) no teclado, só reclamações soltas em fóruns. Também não abri a página oficial da Capcom sobre os controles Modern.
O que isso significa para o Kavo: J/K/L + WASD tem precedente forte (2XKO), mas Z-target + W + J + espaço já são 4 teclas juntas. Isso cai no ghosting de teclados baratos (caso DMC5). O Kavo deve evitar golpes que exijam 3 ou mais teclas além do movimento e usar event.code. O Shift como tecla de jogo pode abrir o Sticky Keys, e golpes de giro com WASD em sequência são difíceis no teclado.
| Serviço | Limite que importa para 1 arquivo de 6,6 MB | Fonte |
|---|---|---|
| itch.io | Aceita um único .html autocontido sem zip, desde que ele não referencie outros arquivos. Num zip: index.html na raiz, até 1.000 arquivos, 200 MB por arquivo, 500 MB no total, nomes sensíveis a maiúsculas. Tem modo "embed na página" ou "clique para tela cheia". | https://itch.io/docs/creators/html5 |
| Cloudflare Pages | Até 25 MiB por arquivo em todos os planos. Plano grátis: 20.000 arquivos e 500 builds por mês. Upload direto arrastando a pasta no painel, sem git (até 1.000 arquivos por esse caminho). | https://developers.cloudflare.com/pages/platform/limits/ · https://developers.cloudflare.com/pages/get-started/direct-upload/ |
| Cloudflare Workers (assets estáticos) | "Requests to static assets are free and unlimited." | https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/ |
| GitHub Pages | Site publicado até 1 GB, banda com limite flexível de 100 GB/mês, 10 builds/hora. Proibido usar como hospedagem grátis de negócio online ou e-commerce. | https://docs.github.com/en/pages/getting-started-with-github-pages/github-pages-limits |
| Netlify (Free) | 300 créditos/mês com limite rígido: deploy de produção = 15 créditos, 1 GB de banda = 20 créditos. Quando acabam, todos os sites ficam pausados com "Site not available". | https://www.netlify.com/pricing/ · https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/how-credits-work/ |
Inferência (contas minhas, sem considerar compressão):
O que isso significa para o Kavo: o caminho mais seguro e sem custo é a Cloudflare (Pages com upload direto, ou Workers com assets estáticos): 6,6 MB cabem nos 25 MiB e requisição de asset estático é grátis e ilimitada. O itch.io é a segunda opção, porque aceita o .html único direto e já dá página de jogo pública. O Netlify Free é arriscado: um post que viralize pode pausar todos os sites da conta.
Data: 19/10/2026. Toda afirmação abaixo vem de página que eu abri, com o link ao lado. Quando a fonte é secundária (resumo automático, guia de fã, fórum), está marcado. O que é proposta minha, e não fato de fonte, aparece como proposta ou inferência.
Como li: abri as páginas com WebFetch, que devolve um resumo, ou baixei o HTML com curl e li o texto bruto. As citações entre aspas em inglês saíram do texto bruto (livro do Haigh-Hutchinson, Iwata Asks, Critical Video Game Studies e TV Tropes). O resto está parafraseado. O TV Tropes bloqueia acesso direto, então li a cópia de 2024 do Wayback Machine. Cópias brutas das páginas usadas ficaram em agentes/pesquisa-cam-fontes/.
O que não achei ou não consegui abrir está no fim, na seção "Lacunas".
O dono diz que a câmera é "porca e confusa, toda hora preciso ajustar manualmente". As fontes concordam em três pontos:
Não existe transcrição oficial. A melhor fonte escrita é uma tradução em russo da palestra no Habr, em duas partes, com os 50 itens numerados. Uso a numeração do Habr. A Game Developer confirma a palestra, de 2014, e linka o vídeo gratuito: https://gamedeveloper.com/design/video-50-common-game-camera-mistakes----and-how-to-fix-them
Os itens que importam para o Kavo:
| # | Erro | Correção do Nesky |
|---|---|---|
| 4 | Distância errada | Ficar perto o bastante para que a linha câmera→personagem seja difícil de bloquear |
| 5 | Obstáculo dos lados | Leque de raios para ver o obstáculo cedo e contornar suave, sem pulo |
| 6 | Afastar a câmera quando o jogador vira para a parede | A intenção do jogador vem primeiro. Aproximar devagar até o obstáculo ficar atrás da câmera |
| 8 | Forças brigando pela câmera | Agrupar por grau de liberdade (yaw, pitch, distância, FOV) e dar prioridade. O jogador ganha sempre |
| 9 | Poste fino tapando a vista | Marcar o que "pode tapar" e o que "não pode". Atravessar ou ignorar os finos |
| 12 | Obstáculo atrás da câmera | Raio para trás. Se houver algo, chegar mais perto do personagem, não girar de lado |
| 13 | Câmera colada corta o personagem | Distância mínima segura. (A paráfrase da Skillbox acrescenta: deixar o personagem semitransparente) |
| 14–16 | Distância, FOV e inclinação soltos | Distância e FOV como função da inclinação: olhando para cima, chega mais perto. Olhando para baixo, recua |
| 18 | Corte que muda o sentido dos controles | Corte só em momento seguro, ou interpolar o sentido do controle entre os planos |
| 20 | Quebrar a regra dos 180° | Não inverter o eixo. Depois de cutscene, voltar devagar para a câmera de jogo |
| 22 | Deixar o jogador cuidar da câmera | Andar e girar câmera ao mesmo tempo é difícil. A câmera tem que prever o que ele precisa ver |
| 23 | Não girar enquanto corre | Girar para a velocidade real do personagem, não para o input. Na parede invisível, girar para mostrar a saída |
| 25 | Câmera reta na beirada | Raio para baixo à frente. Na beirada, inclinar para mostrar a queda |
| 27 | Regra dos terços na hora errada | Personagem no centro enquanto se move (o jogador usa o centro para mirar a direção). Deslocar só parado, como em Journey |
| 29 | Só câmera procedural | Dicas colocadas pelo designer, sobretudo em espaço fechado |
| 31 | Girar para alvo perto | Transladar de lado ou para trás. Girar para alvo perto faz a câmera rodopiar |
| 32 | Transladar para alvo longe | Para alvo longe, girar |
| 34–35 | Dar o controle e tomar de volta | Esperar. Melhor: só retomar depois que o jogador começa a andar |
| 40 | Câmera solta demais | Suavizar, mas com limite rígido de quanto o personagem sai do centro |
| 42 | FOV mudando rápido | Evitar zoom brusco e FOV que abre na aceleração. Dá enjoo |
| 43–44 | Tremida | Slider de intensidade. Tremida de passo, quase nula |
| 45 | Seguir o pulo na vertical | Não seguir cada pulo. Segurar a altura ou esperar o pouso num nível diferente, como no Mario |
| 46 | Mover a câmera rápido | Se tem que ir longe, corte seco. Movimento rápido enjoa tanto quanto corte |
| 50 | Um algoritmo universal | Não resolver tudo com uma função de otimização. Iterar regra por regra, sabendo a prioridade de cada uma |
Fontes: parte 1 (itens 1–25) https://habr.com/en/post/272401 · parte 2 (itens 26–50) https://habr.com/en/post/272825 · paráfrase em russo da Skillbox, com outra numeração https://skillbox.ru/media/gamedev/50-principov-vazhnyh-dlya-sozdaniya-horoshey-igrovoy-kamery/
Na Skillbox aparece um item que o Habr não destaca: em brawler com muitos inimigos, afastar a câmera para ela não ficar pulando de alvo em alvo.
Li o texto do capítulo 4, "Camera Design Guidelines" (pp. 132–136), numa cópia online do livro. As regras, literais:
Trecho oficial sobre oclusão: raios que não chegam ao alvo empurram a câmera. Prefira molas amortecidas a restrições duras, porque restrição dura dá movimento descontínuo. A câmera é uma esfera um pouco maior que o near plane. Câmeras preditivas navegam melhor que as reativas. Game Developer, excerto do livro: https://gamedeveloper.com/design/real-time-cameras---navigation-and-occlusion · prefácio, com a lista de defeitos que as resenhas criticam ("cameras that stutter, lag, shake…, pass through walls, or point in the wrong direction"): https://www.gamedeveloper.com/programming/-real-time-cameras-and-mark-haigh-hutchinson
Câmera colada, com várias soluções em volta. Fonte: resumo automático do vídeo da palestra GDC 2019 de Mihir Sheth (secundária): https://videohighlight.com/v/hE5tWF-Ou2k (vídeo: https://www.youtube.com/watch?v=hE5tWF-Ou2k)
PlayStation Blog, outros estúdios sobre o GoW: "a further pulled back camera to enable greater field of view … it greatly improves legibility of targets", e "Camera shake is also significantly reduced". https://blog.playstation.com/?p=370399
Li src/camera.ts e src/player/controller.ts da cópia em /tmp/kavo-analise/apps/rubro-arena (pode sumir no restart). A câmera já tem muita coisa certa:
yawLatch).Mesmo assim, as fontes apontam cinco causas prováveis para o "toda hora preciso ajustar":
sideW, camera.ts:307–357). Segurar A ou D nunca traz a câmera para trás.controller.ts, yawLatch). Então girar a câmera nessa hora não faria o personagem andar em círculo.camera.ts:437–454). O Nesky #12 manda chegar mais perto, e o Haigh-Hutchinson manda minimizar o movimento que o jogador não pediu.threatBias). O Nesky #31 diz que girar para alvo perto faz a câmera rodopiar. Para alvo perto, transladar ou recuar.Spec por momento. Os números marcados proposta são ponto de partida para afinar, não valores tirados de fonte.
Princípio: uma câmera simples (Haigh-Hutchinson, "the simplest camera solution is usually the best"). Prioridade fixa: jogador > colisão > lock > seguir > enfeite (Nesky #8). FOV fixo fora de cutscene (Nesky #42).
A. Andando e correndo (hominho ou Kavo)
B. Pulo e parkour
C. Rua estreita e beco
D. Dentro do 7-Eleven
E. Combate sem lock
F. Com lock (Shift segurado)
G. Diálogo
H. Henshin
I. Opções (XAG 117): seguir automático ligado ou desligado, com velocidade; tremida de 0 a 100%; FOV; câmera perto ou longe.
Decisão do dono, tomada como dada: o protagonista é um hominho normal e só ganha os poderes depois do henshin.
O trope chama-se "Transformation Is a Free Action": o personagem "will obligingly wait for another character to finish his transformation". Na maioria das vezes, "the transformation really is a Free Action in that it effectively takes near zero time in-universe", e a sequência longa é exagero para encher tempo. Quando um herói contraria o trope, o normal é "making the initial triggering of the transformation the part that can be interrupted instead of the sequence proper".
Exemplos em jogos, todos da mesma página (https://tvtropes.org/pmwiki/pmwiki.php/Main/TransformationIsAFreeAction):
Sobre a repetição na TV: "If a particular transformation sequence occurs multiple times in one episode, a shortened version will often be used after the first time." E "Rarely is there a special-effects sequence for changing back." TV Tropes, Transformation Sequence: https://tvtropes.org/pmwiki/pmwiki.php/Main/TransformationSequence
Repetição em jogo indie: o dev de FLORA admite que a transição "does get a liiiiiittle tedious after the first few times seeing it" e vai encurtar ou deixar pular. https://itch.io/post/10556905
| Jogo | Gatilho | Quanto dura / o que acaba | O que só a forma poderosa faz | Fonte |
|---|---|---|---|---|
| Viewtiful Joe | Medidor VFX cheio. "Henshin-a-go-go, baby!" | Os poderes gastam o medidor. Vazio, volta ao "normal Joe", com menos ataque e defesa, até recarregar | Slow, Mach Speed, Zoom In | https://en.wikipedia.org/wiki/Viewtiful_Joe_(video_game) |
| (idem) | Recarga: sozinho com o tempo, ou garrafas de "VFX Juice" | (idem) | ||
| Devil May Cry 4 | Apertar com o medidor cheio | Temporário | Golpes novos, mais dano, "slow but steady health regeneration" | https://en.wikipedia.org/wiki/Devil_May_Cry_4 |
| Bayonetta 2 | Medidor de magia cheio (Umbran Climax) | Curto | Golpes mais fortes e cura durante o uso | https://en.wikipedia.org/wiki/Bayonetta_2 |
| Asura's Wrath | Burst começa vazio em cada encontro e enche batendo, com dano e QTE | Burst é um golpe final. Unlimited é dano extra temporário | Finalizador que leva a história adiante | https://en.wikipedia.org/wiki/Asura%27s_Wrath |
| Sonic 2 | 7 Esmeraldas + 50 anéis | "loses one ring per second and reverts to normal when his rings are depleted" | Quase invencível, mais rápido, pula mais | https://en.wikipedia.org/wiki/Sonic_the_Hedgehog_2 |
| Yakuza 0 (Kiryu, homem normal) | Heat enche batendo | Esvazia ao apanhar, ser agarrado, cair ou ficar sem atacar | Heat Actions | https://www.ludo.guide/guide/yakuza-0/basic-combat |
| (idem) | Staminan Royale recupera vida e Heat. Vende em drogaria | https://gamersdecide.com/articles/yakuza-0-best-healing-items | ||
| inFAMOUS | Sempre com poder, mas precisa de eletricidade | Recarrega "by draining electricity from powered sources or from living beings". Não encosta em água | Raios. Também escala prédios e cai de alto sem dano | https://en.wikipedia.org/wiki/Infamous_(video_game) |
| Prototype | Disfarce da última pessoa consumida | "The disguise will only last as long as Alex remains inconspicuous" | Detector viral da Blackwatch vê através do disfarce | https://en.wikipedia.org/wiki/Prototype_(video_game) |
| Spider-Man (2018) | Trechos fixos como Peter, MJ ou Miles | Peter faz puzzles. MJ e Miles fazem stealth | Críticas mistas: "hit-and-miss … a bit grating at times" (VideoGamer, sobre esses trechos). A EGM diz que o stealth em geral é "rarely mandatory". Há opção de pular os puzzles | https://en.wikipedia.org/wiki/Spider-Man_(2018_video_game) |
| The Wonderful 101 | Desenhar a forma (Unite Morph) | Gasta bateria, que recarrega batendo ou com baterias dos inimigos | Mais heróis recrutados = morph mais forte. Civis viram heróis quando recrutados. Wonder-Red é o professor Will Wedgewood | https://en.wikipedia.org/wiki/The_Wonderful_101 |
| Persona 5 | Vida escolar de dia e Metaverso à noite | Laços sociais do mundo real aumentam a força das personas nas masmorras (descrição do autor da matéria) | https://vice.com/en/article/precious-moments-hype-and-high-school-a-conversation-with-persona-5-director-katsura-hashino | |
| Kamen Rider: Memory of Heroez | Troca de forma "on the fly" em combate | Cada troca custa "a big chunk of your AP meter, limiting your ability to pull off some insane combos". O EX gauge libera o finalizador | Formas com golpes próprios. A resenha acha repetitivo "get surrounded by a barrier, and defeat enemies" | https://onemoregame.ph/?p=77208 |
O que o civil pode fazer? Andar, conversar, comprar e escalar. O Cole do inFAMOUS escala tudo. Os trechos de Peter e MJ são puzzle e stealth. Persona põe o dia a dia como metade do jogo. A evidência do Spider-Man pede trechos civis curtos e quase nunca obrigatórios.
O que só o herói faz? Nos jogos acima, a forma poderosa ganha dano, golpes novos, cura ou quase invencibilidade (DMC, Bayonetta, Sonic) e poderes únicos (Viewtiful Joe). A forma normal fica mais fraca (Joe) ou exposta (Sonic com 0 anéis).
O que dispara?
Quanto dura e o que acaba?
Quanto pode durar a animação antes de irritar?
Identidade secreta:
Quem é quem
Gatilho (proposta): no civil, segurar K por ~0,4 s, já que o civil não tem golpe de energia. Só funciona com o raio carregado acima de ~30%. É o "medidor cheio + botão" do DMC e do Joe, mais a pose que "aciona o cinturão" dos Riders Shōwa. Não usar combinação de 3 teclas, por causa do ghosting (pesquisa de benchmarks).
Animação (proposta, apoiada em 2.2):
Raio amarelo do peito = Color Timer (proposta, baseado no Ultraman):
Duração e fim (proposta):
O 7-Eleven como posto de recarga:
Identidade: