Repository navigation
GameBoost v2: Fases 0 e 1 - #1
Open
jvaz-DEVENG wants to merge 22 commits into
Open
jvaz-DEVENG wants to merge 22 commits into
jvaz-DEVENG wants to merge 22 commits into
Conversation
O codigo-fonte da v1.0.0 nao existia mais (so o binario de 03/09/2026), entao a Fase 0 virou reconstruir a v1 dentro da arquitetura alvo da secao 4.1 do spec, em vez de refatorar. Motivo e consequencias em docs/DECISOES.md. Estrutura - GameBoost.Core: logica pura, sem WPF, testavel - GameBoost.App: WPF, tema escuro, MVVM com CommunityToolkit - GameBoost.Cli: parser e runner dos comandos de linha - GameBoost.Tests: xUnit + NSubstitute, 109 testes Infraestrutura compartilhada - SafetyGuard: blindagem central consultada por todos os modulos. Processos criticos, anti-cheats, antivirus (WMI SecurityCenter2 + lista estatica), caminhos e servicos protegidos. Comparacao de caminho por segmento, para C:\WindowsApps nao passar por C:\Windows. - RollbackEngine + StateBackup: valor anterior gravado antes de aplicar, reversao em ordem inversa, falha isolada nao interrompe o resto. - Logger estruturado com rotacao, settings com schemaVersion e migradores, modo portatil via portable.txt. Modo Game portado para IModule/ActionItem Varredura com blindagem, encerramento gracioso de 3 s antes do forcado, EmptyWorkingSet + purga da Standby List, plano de energia, prioridade Alta do jogo (nunca Realtime), silenciamento do sistema e pausa do wuauserv. Reabertura dos apps marcados com estrela e aviso quando o app foi fechado com o modo ativo. Dois bugs pegos pelos testes - AppSettings.SchemaVersion inicializado com VersaoAtual fazia um settings.json da v1 se disfarcar de atual: o migrador nunca rodava. - Converter de bool aplicado a uma string deixava o aviso de restauracao invisivel para sempre. Build e testes limpos, zero avisos, inclusive com -warnaserror. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rodar o binario expos tres problemas que os testes nao pegavam. 1. Manifesto invalido (SxS) O comentario do app-dev.manifest citava os parametros de linha de comando, e dois hifens seguidos sao ilegais dentro de comentario XML. O parser do Windows rejeitava o manifesto inteiro e o app nem iniciava, com a mensagem generica "configuracao lado a lado incorreta". O app.manifest de producao nunca teve o problema. Nota no proprio arquivo para nao repetir. 2. Modo CLI nao encerrava Application.Shutdown() dentro de OnStartup nao encerra um app WPF que nunca abriu janela: o loop de mensagens segue vivo e o processo fica pendurado. Corrigido na origem com um StartupObject proprio (Program.cs): o caminho de linha de comando roda sem inicializar o WPF. 3. Regra 3: IDEs e editores vinham pre-marcados A varredura real marcou Antigravity, claude e ChatGPT. Sao apps com trabalho nao salvo aberto. Lista ampliada com editores, IDEs, assistentes de codigo, engines e ferramentas de conteudo, com Theory cobrindo os nomes. Tambem - Build de desenvolvimento sem elevacao via -p:SemElevacao=true, que gera GameBoost-dev.exe com manifesto asInvoker para inspecionar a UI numa sessao comum. Um alvo do csproj impede que isso vire Release. Verificado no binario: --scan real gera relatorio, detecta o jogo aberto (Overwatch) e a UI abre com a lista preenchida. 121 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Menu lateral com as 11 entradas, tema escuro mantido. Nenhum modulo novo: so a casca que as fases seguintes vao preencher. - PageViewModelBase: contrato minimo que o shell conhece. - ModulePageViewModel: padrao de pagina de modulo pronto para reuso, com cabecalho e Varrer, lista generica de ActionItem, rodape fixo com "Aplicar N selecionados" e "Reverter", marcar/limpar e dry-run. Um modulo novo que herde daqui ganha a tela inteira sem escrever XAML. - Template de ActionItem extraido para Themes/Controles.xaml, entao Limpeza, Desinstalador e Inicializacao vao sair identicos ao Modo Game. - InicioPage: a tela do Modo Game virou a pagina Inicio, com o espaco da pontuacao de saude reservado para a Fase 1. - PlaceholderPage: cada modulo ainda nao implementado diz o que vai fazer, cita a secao do spec e mostra "Disponivel na Fase N". As paginas ficam no menu de proposito, para o usuario ver o mapa do produto inteiro. - ConfiguracoesPage com abas Preferencias e Historico. O Historico le direto o state-backup, a mesma fonte do "Reverter tudo", com Desfazer por item. Acessibilidade: sem AutomationProperties.Name os itens do menu se anunciavam para o leitor de tela como GameBoost.App.ViewModels.PlaceholderPageViewModel. Descoberto inspecionando a arvore de automacao da janela real. Glifos dos icones gravados como escapes e nao como caractere literal da Private Use Area, que nao sobrevive a ferramentas que reencodam o arquivo. Requisito novo registrado para a Fase 4 (secao 5.3 do spec): aba "Restos de apps antigos", varrendo AppData e ProgramData por pastas sem app correspondente. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Modulo Bottleneck (secao 5.5) Coleta a cada 1 s com buffer de 5 minutos: CPU, memoria, Standby List, GPU, disco, rede e temperatura. Cada fonte falha isolada e vira "indisponivel" na UI, nunca zero disfarcado de medida. 17 regras de diagnostico como classes IFindingRule, cada uma testada com snapshots sinteticos. O texto e escrito para o usuario: "Chrome esta usando 38% da CPU", nao "cpu_threshold_exceeded". Inclui o detector de reset por atualizacao do Windows, que nenhum concorrente tem. Debounce de 30 s para a lista nao piscar a cada segundo. Relatorio de saude (secao 5.12) Nota 0 a 100 por area com os 5 principais achados, exportavel em HTML de arquivo unico, sem link externo, e em JSON. Disponivel na pagina Inicio e em GameBoost.exe --report. Sem PerformanceCounter por nome traduzido O spec sugere "\Processor Information(_Total)", que num Windows pt-BR nao existe: aqui se chama "Informacoes do Processador". Onde ha API nativa, usa-se API nativa. So a GPU depende de PDH, e via PdhAddEnglishCounter, que vale em qualquer idioma. Custo: de 5,02% para 0,115% de CPU O criterio de aceite e 1,5%. A primeira versao media 5,02% com ciclo de 1039 ms, mais lenta que o proprio intervalo de coleta. - Um PerformanceCounter por instancia de GPU virou uma consulta PDH com curinga: ciclo de 1039 ms para 36 ms. - Process.GetProcesses() abria um handle por processo, 400 vezes por segundo; agora e NtQuerySystemInformation numa chamada so. - Deteccao de jogo saiu do WMI e passou a reaproveitar o que a coleta ja leu. - Interfaces de rede deixaram de ser reenumeradas a cada segundo. A medicao do custo tambem estava errada: Stopwatch mede tempo de parede, entao espera de WMI parecia CPU. Agora e GetThreadTimes, o que exigiu tirar o laco do Task.Delay, porque um await devolve a continuacao em outra thread do pool. O monitor roda em thread dedicada de prioridade reduzida, cedendo lugar ao jogo. Corrigido tambem: AoEntrar estava no comando Navegar, mas o menu troca de pagina pelo binding de SelectedItem. O Diagnostico nao comecava a coletar e o Historico nao recarregava. Verificado na maquina real: --report deu nota 76 com achados corretos (RAM em 95%, plano Balanceado com jogo aberto, 26 itens na inicializacao, Game DVR ligado). 189 testes verdes, build limpo com -warnaserror. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
O primeiro teste rodando o binario de producao elevado encontrou coisas que nenhum teste automatizado pegaria, porque dependem do que esta instalado na maquina. 1. Saida redirecionada era descartada `GameBoost.exe --scan > arquivo.txt` gerava arquivo vazio. O codigo so escrevia quando AttachConsole dava certo, e ele falha justamente quando ja existe redirecionamento. Agora Console.IsOutputRedirected decide primeiro. Isso quebrava qualquer uso em script. 2. Wallpaper Engine era eleito "o jogo" wallpaper64 mora em steamapps\common, entao a heuristica de pasta o elegia com confianca 80. O Modo Game subiria a prioridade de CPU do papel de parede em vez da do jogo. Criada a lista NaoSaoJogos (Wallpaper Engine, SteamVR, emuladores, ferramentas de criacao) e a heuristica de pasta passou a exigir mais de 300 MB: utilitario nao tem porte de jogo. 3. OneDrive e companhia vinham pre-marcados Encerrar sincronizacao de nuvem no meio de um envio deixa arquivo pela metade na nuvem. OneDrive, OneDrive.Sync.Service, FileCoAuth, Dropbox, Google Drive, MEGAsync, Syncthing e Nextcloud entraram em NuncaPreMarcados. 4. Instalador em execucao vinha pre-marcado Um AntigravitySetup rodando aparecia marcado; encerrar corromperia a instalacao. Deteccao por padrao de nome, nao por lista, porque instalador novo aparece toda semana. 5. ksdeui, kpm e klnagent nao eram reconhecidos como antivirus Componentes do Kaspersky que o SecurityCenter2 nao reporta. Entraram na lista estatica de seguranca. Verificado no binario elevado: pre-marcados caem de 45 para 37, exatamente os oito que nao deviam estar la, e a varredura passa a dizer "nenhum jogo detectado" em vez de apontar o papel de parede. 213 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Crash da UI (XamlParseException)
Themes/Controles.xaml usa {StaticResource BoolParaVisibilidade}, mas os
conversores eram declarados direto no App.xaml. O WPF processa
MergedDictionaries ANTES das entradas do dicionario que os mescla, entao o
Controles.xaml era lido antes dos conversores existirem.
Como DataTemplate resolve tarde, o erro nao aparecia na abertura: estourava so
ao renderizar o primeiro item de uma lista, e derrubava o app inteiro com
APPCRASH. Nenhuma captura anterior pegou isso porque todas foram feitas com a
lista vazia. Os conversores foram para Themes/Conversores.xaml, mesclado antes.
Handlers globais de excecao
O app morria sem deixar rastro: o log parava na ultima acao bem-sucedida e nao
havia stack trace em lugar nenhum, so um APPCRASH no Visualizador de Eventos.
Agora DispatcherUnhandledException, UnhandledException e UnobservedTaskException
registram o stack completo, e um erro de tela mostra aviso sem derrubar o app.
Foi o que permitiu achar o bug acima.
CI: passo do instalador
$env:ProgramFiles(x86) nao e sintaxe valida em PowerShell: ele le
"$env:ProgramFiles" e deixa "(x86)" solto, procurando em
"C:\Program Files(x86)". Trocado por ${env:ProgramFiles(x86)}, com busca em
tres caminhos e erro explicito quando o ISCC nao aparece, em vez de seguir
adiante depois de o Chocolatey instalar 0 de 1 pacote.
Verificado na maquina real: Inicio e Diagnostico renderizam com as listas
cheias, sem excecao no log. Nota 76, cinco medidores ao vivo com GPU em 14% e
1,9 GB de VRAM, e a coleta custando 0,10% de CPU.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Primeiro teste do ciclo completo com privilegios, usando o binario de producao. Para nao arriscar nada da maquina, os 129 processos existentes foram para NuncaEncerrar e o unico alvo foi um app descartavel compilado para o teste. Resultado, com o estado conferido direto no registro e no powercfg: - GameDVR_Enabled: 1 -> 0 -> 1 - UseNexusForGameBarEnabled, ShowStartupPanel e ToastEnabled: nao existiam, foram criados como 0 e na reversao foram APAGADOS, nao zerados - Plano de energia: Equilibrado -> Alto desempenho -> Equilibrado - wuauserv: ja estava parado e nao foi tocado - O app marcado com estrela foi encerrado e reaberto state-backup.json terminou com zero pendencias. Ganho medido na ativacao: RAM livre de 1,3 GB para 3,9 GB, 13,2 GB retirados do working set de 462 processos. Apagar o que nao existia, em vez de gravar zero, e a diferenca entre reverter de verdade e deixar rastro. Era o unico caminho do produto que nenhum teste automatizado conseguia cobrir. Tambem: AppPaths usava Assembly.Location como fallback, que devolve string VAZIA num publish single-file, e ?? nao trata vazio. O fallback quebraria justamente no formato em que o app e distribuido, que e onde o modo portatil importa. Apontado pelo aviso IL3000 no CI, agora verde. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Limpeza de temporarios e caches da secao 5.2, com a pagina no shell e o comando --clean. Duas camadas de protecao, sempre O caminho precisa vir do CleanupCatalog e, mesmo assim, cada arquivo passa pelo SafetyGuard antes de sair do disco. O modulo nunca deriva caminho por conta propria. Whitelist de leitura separada da de remocao Downloads fica fora da whitelist de remocao de proposito: e pasta do usuario. Mas a varredura precisa poder olhar la para dizer "voce tem 3,7 GB de instaladores velhos". Separar as duas permite mostrar sem nunca ganhar permissao de apagar. Achado por um teste que falhou: com uma whitelist so, o item de sugestao simplesmente nunca aparecia. Honestidade em cada item Cada alvo diz o efeito colateral antes: shader cache avisa que os jogos vao engasgar recompilando, prefetch avisa que ganha pouco e raramente compensa, dump de memoria avisa que e o que um tecnico leria. Ha teste exigindo que todo alvo de risco medio ou alto tenha advertencia e nunca venha pre-marcado. Cache de navegador nunca toca em cookie, senha ou historico, e o item aparece bloqueado enquanto o navegador estiver aberto. Ha teste conferindo os dois. RevertAsync diz a verdade em vez de fingir que desfaz: o que foi para a Lixeira se restaura por ela, e temporario apagado e recriado pelos programas. Botao que finge reverter seria pior que nao ter botao. A pagina nao tem XAML proprio LimpezaPageViewModel so declara nome, icone e o texto de confirmacao: a tela inteira vem do ModulePageViewModel criado com o shell. Era exatamente a promessa daquele padrao, agora confirmada por um modulo de verdade. Verificado no disco real: 22,3 GB encontrados, 4,8 GB no preset seguro, com os shaders (16,2 GB) desmarcados por risco, Downloads bloqueado e os navegadores abertos bloqueados. 230 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
As dez ferramentas da secao 5.13, agrupadas por categoria, cada uma dizendo o que faz, o risco, quanto tempo leva e como desfazer. Sem chamar executavel quando existe API (regra 9) - `ipconfig /flushdns` virou DnsFlushResolverCache, que e o que o proprio ipconfig chama por dentro. - `Checkpoint-Computer` do PowerShell virou WMI SystemRestore: mesma API, sem depender de politica de execucao. - Esvaziar a Lixeira usa SHEmptyRecycleBin, e a consulta previa diz quanto vai sair antes de perguntar. sfc e DISM sao acompanhados ao vivo. Sem isso o usuario acha que travou e mata o app no meio de uma verificacao de 20 minutos. A saida do sfc e lida como UTF-16, senao vira caractere solto. Teste de disco com WriteThrough Sem desligar o cache de escrita, a medicao mostraria a velocidade da RAM e um HDD pareceria SSD. O arquivo de 1 GB e apagado num finally, entao sai tambem se o teste falhar ou for cancelado. O resultado e traduzido: abaixo de 150 MB/s o app diz que aquilo e disco mecanico e que jogo novo vai engasgar. A pagina nao herda o padrao de modulo de proposito: aqui nao ha lista de itens para marcar e aplicar em lote, e forcar o contrato daria um ApplyAsync que nao faz nada e um RevertAsync que mente. Testado na maquina real: informacoes do sistema leem dados de verdade e o teste de disco grava, le e apaga 1 GB sem deixar rastro. 243 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A secao 5.4 pede leitura da MFT com o criterio de 500 GB e 1 milhao de arquivos em menos de 30 s. Por que nao a MFT crua FSCTL_ENUM_USN_DATA devolve nome, pai e atributos, mas NAO devolve o tamanho do arquivo. Para os tamanhos seria preciso parsear os registros da MFT a mao, lendo os atributos $DATA. E o que o WizTree faz, e e bem mais trabalho e risco do que a secao sugere. O caminho escolhido e o que o proprio spec autoriza como alternativa: FindFirstFileEx com FIND_FIRST_EX_LARGE_FETCH e FindExInfoBasic, paralelizado por subarvore com fila de trabalho. O tamanho ja vem na enumeracao, sem uma chamada extra por arquivo. Medido nesta maquina, no disco do sistema: 1.548.745 arquivos, 809,5 GB somados, em 20,3 s. Um milhao e meio de arquivos e 809 GB em 20 segundos: acima do volume do criterio e abaixo do tempo. O contador de pastas inacessiveis estava mentindo A primeira versao reportava 40.871 pastas "sem acesso". O numero real e 543: eu contava toda pasta vazia como inacessivel, porque FindFirstFileEx devolve lista vazia nos dois casos. Agora separa pelos codigos de erro do Windows. Reportar 40 mil problemas onde ha 543 e numero inventado, que a regra 4 proibe. Outras decisoes - Reparse point e ignorado: segui-lo contaria o mesmo espaco duas vezes e pode virar laco infinito. - Prefixo \?\ em todo caminho, senao a varredura para nos 260 caracteres, o que acontece em qualquer node_modules. - Consolidar e iterativo, com teste de 20 mil niveis de profundidade que estouraria a pilha numa versao recursiva. - Arquivos especiais (hiberfil.sys, pagefile.sys, Windows.old) sao explicados em vez de so listados: sem isso o usuario tenta apagar na mao e nao consegue. 261 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fecha a Fase 3 com a interface do analisador de espaco. Treemap Algoritmo squarified, e nao fatiamento simples: ele escolhe a cada passo a direcao que deixa os retangulos mais proximos de quadrados. Fatia comprida e fina e impossivel de clicar e nao deixa comparar areas a olho, que e para o que o treemap serve. Desenha em OnRender porque um nivel de disco cheio tem milhares de blocos e criar objetos visuais para cada um travaria a navegacao. Biblioteca de jogos Le Steam (libraryfolders.vdf e appmanifest_*.acf), Epic, GOG e Xbox. Nesta maquina achou 14 jogos somando 974 GB, com tamanho e quando foi jogado. Jogo grande parado ha mais de 6 meses ganha selo: e onde estao os GB de verdade. A arvore custava 1,1 GB de RAM DiskNode guardava o caminho completo em cada no. Numa arvore de 1,5 milhao de arquivos isso passa de 240 MB so de strings, para uma informacao que ja esta na estrutura: o caminho e a sequencia de nomes ate a raiz. Agora so a raiz guarda caminho completo. Medido: 594 MB, contra 1118 MB. Ao corrigir apareceu uma armadilha: o scanner montava o caminho de cada filho a partir de pasta.Caminho, o que passou a subir a arvore inteira POR ARQUIVO e tornaria a varredura quadratica na profundidade. Agora o caminho da pasta e calculado uma vez por pasta, e o construtor foi separado em dois, um para raiz e um para filho, para que esse erro nao seja possivel de escrever de novo. Honestidade na tela O rodape declara quantas pastas o Windows recusou e quantos GB faltam para fechar com o espaco ocupado, em vez de apresentar o total mapeado como se fosse o disco inteiro. A pagina nao apaga nada: abre no Explorer ou manda para a Lixeira, com confirmacao e dizendo que da para restaurar. 262 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Inventario (secao 5.3) Le o registro Uninstall nas tres chaves, os pacotes da Store e o UserAssist para saber o ultimo uso. Nesta maquina: 217 apps, sendo 113 da Store, 78 protegidos, 5 sugeridos e 166 com desinstalacao silenciosa. Dois bugs que so apareceram rodando 1. Get-AppxPackage devolvia zero pacotes numa maquina com 142 instalados. Duas causas somadas: o comando ia por -Command com pipes e cifroes, que o shell reinterpretava, e o stderr estava redirecionado sem ninguem lendo. O PowerShell escreve barras de progresso em CLIXML no stderr; o buffer enchia, o processo travava e caia no timeout de 30 s. Agora vai por -EncodedCommand, os dois fluxos sao lidos ao mesmo tempo e o ProgressPreference e silenciado. 2. A varredura de restos acusava BraveSoftware e EpicGamesLauncher como sobra, e os dois estao instalados: so escrevem a pasta sem os espacos do nome. E listava "Programs" com 9 GB, que e pasta guarda-chuva do Windows com varios apps ativos dentro. O casamento passou a normalizar espacos e pontuacao e a aceitar prefixo a partir de 5 caracteres, e as guarda-chuva entraram na lista de ignoradas. Resultado caiu de 53 pastas e 26 GB para 30 pastas e 3,9 GB. Falso positivo aqui e pior que falso negativo: deixar de listar um resto custa espaco, apontar um app ativo como resto custa os dados do usuario. Por isso, e porque a heuristica nunca sera perfeita, nada e pre-marcado e a remocao vai para a Lixeira. Protecao documentada docs/PROTECAO.md ganhou as duas listas pedidas: o que nunca sai no desinstalador (runtimes, drivers, WebView2, servicos do Xbox, antivirus) e as pastas que a varredura de restos nunca lista. 265 testes verdes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fecha a secao 5.3 e a 5.6 do spec.
Desinstalacao sempre pelo desinstalador do proprio app, na ordem
QuietUninstallString -> msiexec /x {GUID} /qn -> Remove-AppxPackage ->
desinstalador com janela. Dez minutos de limite por app; saidas 0, 3010
e 1605 contam como sucesso. Depois de cada remocao, varredura do que
ficou para tras, com remocao para a Lixeira.
A aba "Restos de apps antigos" varre %APPDATA%, %LOCALAPPDATA% e
%PROGRAMDATA% atras de pastas sem app correspondente. A lista de pastas
ignoradas esta em docs/PROTECAO.md.
A inicializacao le chaves Run, pastas Inicializar e o StartupApproved.
Desativar grava o mesmo formato de 12 bytes do Gerenciador de Tarefas,
com ChangeRecord antes de cada escrita.
Nada e pre-marcado em nenhuma das tres listas (regra 3).
Quatro correcoes que so a maquina real mostrou:
- O resumo anunciava 1812,4 GB num disco de 953 GB. Pasta generica
declarada como InstallLocation media o disco inteiro, e apps que
declaram a mesma pasta eram contados um por um. Agora sao 1044 GB
para 2145 GB de disco.
- A lista vinha ordenada pelo tamanho declarado no registro, medido so
depois: o ARK, com 309 GB reais, ficava atras do AutoCAD, que declara
4 GB.
- App da Store era acusado de "sem assinatura digital". O executavel
mora em WindowsApps, onde a ACL barra ate o administrador: a
assinatura nao reprovou, nao deu para verificar. Assinado virou
tri-estado.
- WinVerifyTrust rodava duas vezes por entrada, e o impacto no boot
aparecia duas vezes na mesma linha.
Medido: 217 apps (113 da Store, 78 protegidos), 30 pastas de resto
somando 3,9 GB, 26 entradas de inicializacao (4 bloqueadas, 8
sugeridas). Suite em 272 testes, todos passando.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fecha as secoes 5.7, 5.8, 5.9 e 5.10 do spec. A pagina Tweaks junta 12 ajustes do catalogo e 12 servicos do Windows numa lista so. Cada linha diz o efeito real, nao a promessa: quando o ganho e marginal, esta escrito que e marginal. O isolamento de nucleo aparece explicado e nao e alterado; as mitigacoes de Spectre/Meltdown nao entram nem como item bloqueado, porque item na tela vira ideia na cabeca de alguem. SysMain, os servicos do Xbox e o Windows Update aparecem bloqueados para explicar por que nao se deve mexer. A pagina Rede responde duas perguntas. Mede latencia, jitter, perda e compara o DNS em uso com Cloudflare, Google e Quad9 por consulta UDP montada a mao. E descobre o tipo de NAT com um cliente STUN proprio, dois servidores por duas portas locais, classificado no vocabulario dos jogos: aberto, moderado, estrito. Quando e estrito, diz por que: CGNAT pela faixa 100.64.0.0/10, NAT duplo por saltos privados no traceroute, UPnP por SSDP, Teredo pelo registro, firewall por perfil. Reativar o Teredo e reversivel e resolve o caso mais comum de "Game Pass nao conecta": alguem desativou seguindo tutorial de otimizacao. Trocar o DNS volta inclusive para automatico (DHCP). Resetar o Winsock vem com risco Alto e o aviso de que nao tem desfazer. O teste de velocidade usa 3 amostras e reporta a mediana, e depende de permissao explicita em Configuracoes, desligada por padrao: gasta cerca de 100 MB de franquia. docs/PORTAS.md traz as portas por launcher e por jogo, o que fazer em CGNAT e em NAT duplo, e onde fica UPnP em sete marcas de roteador. Quatro correcoes que so a maquina real mostrou: - A leitura da hibernacao procurava C:\hiberfil.sys. File.Exists devolve false para aquele arquivo mesmo com a hibernacao ligada, porque a ACL barra a consulta sem elevacao: o app diria "ja desligada" para todo mundo que abrisse sem ser administrador. - O badge "Protegido" aparecia em ajuste que so precisava de elevacao. Protegido e o GameBoost se recusando a mexer; precisar de admin some ao reabrir elevado. - O driver de video listava o adaptador virtual do Hyper-V, carimbado com 21/06/2006: a tela anunciaria um driver de 242 meses numa maquina com driver do mes passado. - A lista de quem usa a rede repetia o mesmo nome uma vez por PID. O spec foi substituido no meio da fase. Os conflitos entre o que ja estava escrito e o texto novo estao em docs/DECISOES.md, com o que foi corrigido (vocabulario do NAT, duas portas locais, semaforo em 30/80 ms, mediana de 3 amostras) e o que ficou pendente. A aba "Atualizacoes" via winget e da Fase 4, ja entregue, e entra como complemento retroativo com commit proprio. Medido: NAT aberto, IP publico real, 1 salto privado, UPnP disponivel, Teredo qualificado; GTX 1660 Ti com driver 616.64 de 0 meses; 24 ajustes no catalogo, 1 ja ativo. Suite em 282 testes, todos passando. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entrega retroativa. A secao 5.3 do spec ganhou a aba de atualizacoes depois que a Fase 4 ja estava commitada; vem em commit proprio para o historico nao mentir sobre o que cada fase entregou. A aba lista o que tem versao nova em cima do winget, o gerenciador de pacotes da propria Microsoft: quem baixa e confere assinatura e ela, nao o GameBoost. Cada item e classificado e agrupado. Driver fica bloqueado, o GameBoost nao instala driver nem quando o winget se oferece. Runtime e "atualiza sozinho" vem com risco medio e o texto que explica por que tratar diferente. Navegador, compactador, leitor de PDF e Java desatualizados sao marcados como atualizacao de seguranca. Atualizacao e uma a uma, com progresso por item. `winget upgrade --all` seria mais curto e perderia o que importa: com --all um app que falha some no meio da saida e nao da para dizer qual foi nem por que. A aba "Restos de apps antigos" existia no Core desde a Fase 4 mas nunca tinha sido ligada a uma tela. Agora a pagina de Apps tem tres abas, cada uma com o seu proprio botao de acao: aplicar na aba de atualizacoes nao desinstala nada. A saida do `winget upgrade` nao tem formato estruturado: nem JSON, nem XML, so uma tabela de largura fixa com cabecalho traduzido. Nesta maquina o winget imprimiu os cabecalhos em ingles e traduziu os nomes dos pacotes, na mesma saida. A leitura e por posicao de coluna, sem ler palavra nenhuma, e o teste passa a mesma tabela com cabecalho em portugues. Tres correcoes: - O template de TabControl que escrevi para o tema escuro apagou a arvore de acessibilidade: as abas apareciam, mas nenhum controle dentro delas era exposto. Leitor de tela nao alcancaria nada. Faltava o ContentPresenter chamado PART_SelectedContentHost, que e o nome que o TabControlAutomationPeer procura. So apareceu porque a automacao que dirige os testes de tela e a mesma que um leitor de tela usa. - O casamento de id era por prefixo cru, entao "Google.Chrome" pegava "Google.ChromeRemoteDesktopHost": o host de acesso remoto aparecia como navegador e como atualizacao de seguranca. Agora exige fronteira de ponto. Git.GitLFS e Microsoft.EdgeWebView2Runtime cairiam no mesmo erro e estao no teste. - As abas saiam ilegiveis: o TabItem padrao do WPF usa fundo claro e texto escuro. Medido: winget 1.29.290, 35 atualizacoes, 4 de seguranca, 7 que se atualizam sozinhos; 30 pastas de resto somando 3,8 GB. Suite em 296 testes, todos passando. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fecha as secoes 5.1 (melhorias) e 5.11 do spec, mais a bandeja e o onboarding da secao 6. Um perfil diz o que fazer quando o jogo abre e o que desfazer quando ele fecha. A reversao nao e opcional nem configuravel: sair do jogo devolve a maquina ao estado anterior, sempre. Um perfil que so soubesse ligar coisas seria a mesma armadilha dos boosters que deixam o Windows Update desligado para sempre. Um arquivo por perfil em profiles/*.json: perfil corrompido derruba aquele jogo, nao a colecao inteira. A deteccao vigia os processos a cada 2 s numa thread de prioridade baixa, cruzando nome conhecido, pasta de launcher e janela em tela cheia. Perfil novo nunca ativa sozinho — o convite aparece, e so quem marcar "ativa sozinho" passa a ser aplicado sem perguntar. Afinidade de CPU e timer de 0,5 ms entram por perfil, os dois desligados por padrao. O texto do timer diz que ele vale menos do que a internet promete: desde o Windows 10 2004 o pedido e por processo, entao o GameBoost pedir 0,5 ms afeta o GameBoost, nao o jogo. A bandeja tras o menu da secao 6. Fechar a janela esconde nela, com aviso na primeira vez; "Sair" sai de verdade. Programa que some sem dizer para onde foi e o motivo de as pessoas irem no Gerenciador de Tarefas. O onboarding de tres telas diz o contrato, nao vende FPS: tudo e reversivel, nada sai daqui, e a maior parte do que os otimizadores prometem nao existe. Duas correcoes: - UseWindowsForms, necessario para o NotifyIcon, injeta using global de System.Windows.Forms e System.Drawing em TODO arquivo do projeto. Control, Application, Brush e MouseEventArgs viraram ambiguos na aplicacao inteira, inclusive no Treemap e no MiniGrafico, que nao tem nada a ver com bandeja. Os usings implicitos saem no csproj; o arquivo da bandeja declara os dele. - O texto de estado vazio da pagina Jogos aparecia por cima da lista cheia: a visibilidade dependia do Resumo, que nunca fica vazio. Sair do app desfaz o perfil aplicado antes de encerrar. Sem isso, prioridade e tweaks ficariam de pe depois que o processo sumiu. Medido: 14 jogos e 974,2 GB na biblioteca, 20 nucleos, varredura de janelas em 3 ms, icone na bandeja e vigia confirmados no log. Suite em 302 testes, todos passando. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fecha a secao 10 do spec e a ultima fase do projeto. Auto-update com Velopack apontando para as releases do GitHub: checagem silenciosa na abertura, download em segundo plano, e a troca de versao so no proximo inicio ou quando o usuario mandar. O GameBoost nunca troca de versao no meio do que a pessoa esta fazendo. VelopackApp.Build().Run() e a primeira linha do Main, antes de ler argumentos. O Velopack roda o proprio exe para instalar e atualizar, e essa chamada precisa interceptar: depois de qualquer inicializacao, a instalacao abriria a janela do app no meio do update. O workflow de release dispara em tag gameboost-v*, confere que a tag bate com o Directory.Build.props, testa, publica, assina quando ha certificado, empacota com o Velopack, monta instalador e portatil, gera SHA256SUMS e anexa tudo a release. O passo de assinatura e condicional de proposito. docs/ASSINATURA.md registra o que foi apurado: o Azure Artifact Signing, que seria a opcao de US$ 9,99/mes, nao aceita Brasil nem como pessoa fisica nem como juridica; certificado OV de CA e o caminho viavel, com a chave em token USB ou HSM desde jun/2023; e EV nao pula mais o SmartScreen desde 2024, entao nao vale pagar. Sem o segredo, a release sai sem assinatura e o CI avisa, em vez de falhar a build inteira por causa de um certificado que ainda nao existe. O instalador passou a perguntar entre instalar para todos os usuarios ou so para o atual, e a chave de autostart foi de HKCU para HKA, que resolve para a raiz certa nos dois casos. Tres correcoes: - A here-string do PowerShell exige o terminador na coluna 0, e o YAML exige todo conteudo indentado. As duas regras nao coexistem: o '@ em coluna 0 encerrava o bloco no meio do script e o arquivo deixava de ser YAML valido. Pego validando com parser antes do commit. - O .iss tinha #define AppVersion fixo, entao o /DAppVersion do CI seria ignorado em silencio. - O UseWindowsForms da Fase 6 ligou os analisadores do WinForms, e o WFO0003 manda tirar a configuracao de DPI do manifesto — regra que nao se aplica a app WPF. Com o -warnaserror do CI, quebraria a build de Release inteira. A tabela "O que vem depois" do README estava corrompida desde a Fase 5: a linha da fase 5 foi removida por cima do separador. Como nao ha mais fase depois, a secao virou "Como instalar". Medido: modo portatil confirmado com o binario real, data\ ao lado do exe e nada em %LOCALAPPDATA%. Build de Release limpa com -warnaserror. Suite em 304 testes, todos passando. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Primeira rodada do checklist de docs/QA.md com o binario elevado na maquina real. Seis problemas nos tres primeiros passos, nenhum deles visivel nos 304 testes automatizados nem nas capturas de tela das fases. Os dois piores eram de ciclo de vida: - A sessao nunca era encerrada. ReverterTudo desfazia tudo e limpava a tela, mas nao tocava no session.json. O arquivo continuava dizendo Ativo=true, e o aviso "fechado com o Modo Game ainda ativo" voltava em toda abertura, com zero pendencias. O estado do registro estava perfeito, que e justamente o que os testes conferem. - Dispose chamado duas vezes. O App.OnExit descartava o GameWatcher e o ServiceProvider.Dispose() descartava de novo, porque o container descarta todo singleton IDisposable. A segunda chamada caia num CancellationTokenSource ja descartado, e a excecao escapava porque o ServiceProvider.Dispose() estava fora do try. Resultado: caixa de erro em toda saida. Dispose agora e idempotente. Os outros quatro eram de interface dizendo uma coisa e fazendo outra: - O rodape anunciava "N aguardando na lista de restauracao" sem existir lista. Agora ha o bloco "Aguardando para reabrir", com botao por app. - O resumo dizia "38 apps marcados" com um item marcado, porque era o texto da varredura e nao acompanhava a selecao. - Faltava marcar/desmarcar todos na tela Inicio. - Faltava busca em qualquer lista. Entrou na classe base, entao vale para todas as paginas de modulo; ignora acento e caixa, e filtrar nao desmarca nada. Os dois ultimos vieram como sugestao de quem testava. A busca ja estava na secao 5.3 do spec e passou despercebida por sete fases. Validado na maquina, um a um, com o binario republicado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Segunda rodada do checklist, nos blocos de tweaks e rede. O achado principal e uma decisao errada minha, da Fase 5. O spec pedia "3 amostras de 10 s" e eu troquei por 25 MB cronometrados, achando equivalente e mais previsivel para quem tem franquia. Deixei escrito como se fosse melhoria. Nao e equivalente. Numa fibra domestica a operadora libera bem acima do contratado por um ou dois segundos, e uma transferencia que termina em 0,6 s mede so essa rajada. Na mesma linha e no mesmo minuto: 8 MB | 0,51 s | 131,5 Mbps 25 MB | 0,59 s | 354,7 Mbps 50 MB | 1,53 s | 274,9 Mbps Tres respostas para a mesma pergunta. Um teste cujo resultado depende do tamanho escolhido nao esta medindo a linha. Agora mede por tempo (8 s por amostra) e descarta os 2 primeiros segundos, onde moram a rajada e o slow start do TCP. O erro caiu de +44% para -15% contra um teste dedicado, e -15% e o resultado certo para um teste de um servidor so e uma conexao so. No meio disso eu errei a leitura: vi 380 contra 73 e afirmei que 380 era impossivel e 73 era o numero real. Era o servidor de Cotia limitando. Trocando de servidor, o mesmo teste dedicado deu 264 e o ping caiu de 361 ms para 4 ms. A correcao continua valendo, mas o diagnostico estava mal fundamentado; docs/DECISOES.md registra os dois. Outras tres: - A lista nao atualizava depois de agir. O badge de um servico continuava dizendo "automatico" com ele ja em "manual": a tela parecia nao ter feito nada. Onde a varredura e barata (Tweaks) ela refaz sozinha; onde e cara (Apps) a tela avisa que mostra o estado de antes. Ou a tela mostra o estado atual, ou diz que nao esta mostrando. - Markdown cru na tela: **negrito** aparecia com os asteriscos, porque o WPF nao renderiza markdown. - Glifos de icone do menu se perdiam a cada reescrita do arquivo. Agora estao em escape \uXXXX, que e ASCII puro. Validado na maquina: tweak de HKCU com valor existente, tweak com valor ausente (apagado e nao zerado), servico Automatic -> Manual -> Automatic, e teste de velocidade contra dois servidores diferentes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ao confirmar o envio de uma pasta para a Lixeira, o GameBoost fechou. Sem caixa de erro e sem excecao no log. O Visualizador de Eventos mostrou 0xc0000005 no proprio exe: violacao de acesso, crash nativo, por isso os tres handlers globais nao pegaram nada. A causa era uma linha: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] private struct SHFILEOPSTRUCT Em x64 os campos dessa struct sao alinhados em 8 bytes. Com Pack = 1 o .NET remove o preenchimento e o pFrom passa a ficar no deslocamento 12. O shell32 continua lendo do 16: pega a metade alta de um ponteiro colada na metade baixa do seguinte e usa aquilo como endereco. A Lixeira nunca funcionou em lugar nenhum. Limpeza, Espaco e Restos chamam a mesma funcao. Tres modulos, tres fases, e em todos a acao de mandar para a Lixeira derrubaria o aplicativo. O docs/QA.md tinha o item desde a Fase 2, sem marcar: - [ ] Enviar um arquivo para a Lixeira e conferir que da para restaurar Nao era detalhe cosmetico esperando na fila: era a unica forma de descobrir um crash garantido. O teste novo manda um arquivo de verdade e confere que ele saiu da origem. Marshalling errado nao quebra a compilacao e nao aparece em teste com fake; so uma chamada real pega. Validado na maquina, no mesmo bloco de QA: - Call of Duty desinstalado: 180,3 GB, 217 -> 216 apps, 1044 -> 863,7 GB - qwen-updater para a Lixeira, restauravel, com o caminho de origem - 7-Zip atualizado de 24.09 para 26.03, com updates-history.json gravado Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A tela passa GUIDs de ChangeRecord para RevertAsync. O StartupModule esperava NOMES de programa e tentava casar com id.Contains(e.Nome). Um GUID nunca contem "WallpaperEngine": a lista de alvos saia vazia, nada era desfeito, e o metodo retornava sucesso. A tela dizia "Pronto" e o item continuava desativado no registro e no Gerenciador de Tarefas. Alem de errado, aquilo dependia de _ultimaLista estar preenchida, ou seja, de alguem ter varrido na mesma sessao. Reabrir o app e clicar em Reverter nao funcionaria nem com o casamento certo. Agora usa o RollbackEngine, que le o proprio ChangeRecord: a chave, o nome do valor, o conteudo anterior e se ele existia. E o mesmo caminho que os Tweaks ja usavam. Teste novo cobre o ciclo inteiro com uma entrada descartavel: desativa, confere os 12 bytes com o primeiro em 0x03, reativa e confere que voltou. Reativar pode apagar o valor em vez de gravar 0x02, e as duas formas valem — no StartupApproved a ausencia significa habilitado. Validado na maquina: WallpaperEngine desativado apareceu como "Desabilitado" no Gerenciador de Tarefas, e depois da reversao voltou a "Habilitado" nos dois lados, com 0 pendencias. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seis correcoes no bloco de perfis, e o padrao entre elas diz mais que
cada uma isolada.
A deteccao estava certa desde a Fase 6. O log registrava exatamente o que
o spec pede: "executavel dentro de uma pasta de launcher, confianca 80,
tela cheia True". O que nao existia era o resto do caminho.
- O convite era disparado num evento SEM NENHUM ASSINANTE. Nascia e
morria na mesma linha: nem balao, nem faixa, nada. Agora vai para a
bandeja e para uma faixa na janela — com o jogo em tela cheia o Windows
engole o balao, e a faixa espera.
- Aceitar o convite so navegava para outra tela. O ProfileRunner, a
classe inteira escrita para aplicar o perfil, nunca era chamada por
ninguem.
- Fechar o jogo tambem nao chamava o ProfileRunner, mas a mensagem
afirmava que "tudo o que o GameBoost alterou foi desfeito". Afirmar
reversao sem reverter e o oposto da regra 1.
Tres elos de uma corrente de quatro. O primeiro funcionava e os outros
tres nao existiam. Nada disso aparece em teste unitario: cada peca
isolada faz o que promete, e o ProfileRunner tem teste e passa. O que nao
tinha teste era alguem chamando o ProfileRunner.
Mais tres:
- O perfil nao casava com o jogo. O perfil e chaveado pelo executavel
("warframe.x64") e o casamento usava o nome da pasta ("Warframe"). A
mesma linha aparecia duas vezes, uma dizendo que tem perfil e a outra
que nao tem. Agora casa pela pasta de instalacao.
- Falso positivo: o Agent do Battle.net foi anunciado como jogo numa
sessao real. Ele mora em ProgramData\Battle.net\Agent, que bate com a
lista de launchers, e engorda enquanto baixa atualizacao — passou do
filtro de 300 MB. Filtro de memoria distingue processo grande de
pequeno, nao jogo de instalador. O que separa e o nome: jogo tem nome
proprio, infraestrutura tem nome de funcao.
- "Nao havia perfil aplicado" quando havia. Quando o jogo fecha antes do
Desfazer, nao sobra prioridade para restaurar: ela morre com o
processo. O texto negava um fato para descrever uma consequencia.
Validado na maquina, ciclo completo: Warframe detectado em 2 s, convite
na faixa com o nome do perfil, prioridade Normal -> AboveNormal ao
aceitar, e o jogo aparecendo na lista do Modo Game como Protegido com
"Este parece ser o jogo" e caixa desabilitada.
Suite em 312 testes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Traz o GameBoost para dentro deste repositório e entrega as duas primeiras fases do
GAMEBOOST_V2_SPEC.md.O código-fonte da v1.0.0 não existia mais — só o binário de 03/09/2026. A Fase 0 virou reconstruir a v1 dentro da arquitetura alvo, em vez de refatorar. Motivo e consequências em
docs/DECISOES.md.O que entra
Fase 0 — fundação
GameBoost.Core(sem WPF, testável),GameBoost.App(WPF),GameBoost.Cli,GameBoost.TestsSafetyGuard: blindagem central consultada por todos os módulosRollbackEngine: valor anterior gravado antes de aplicar, reversão em ordem inversa, falha isolada não interrompe o restoIModule/ActionItemShell de navegação — as 11 entradas da seção 6, com o padrão de página de módulo pronto para reuso: um módulo novo que herde de
ModulePageViewModelganha a tela inteira sem escrever XAML.Fase 1 — diagnóstico e relatório de saúde
GameBoost.exe --reportDesempenho
O critério da seção 5.5 é menos de 1,5% de CPU. A primeira versão media 5,02% com ciclo de 1039 ms. Depois de trocar contadores por consulta PDH com curinga,
Process.GetProcesses()porNtQuerySystemInformatione tirar o WMI da detecção de jogo: 0,115% de CPU, ciclo de 36 ms.A medição também estava errada —
Stopwatchmede tempo de parede, então espera de WMI contava como CPU. Trocada porGetThreadTimes, o que exigiu mover o laço para uma thread dedicada.Achado por rodar de verdade
Testar o binário elevado numa máquina real encontrou cinco problemas que nenhum teste automatizado pegaria:
--scan > arquivo.txtgerava arquivo vaziosteamapps\commonTodos corrigidos, com teste de regressão.
Validação
-warnaserror--scan,--reporte os dry-runs verificados no binário de produção, elevadoO que não foi testado
docs/QA.md, incluindo o crítico: ativar o Modo Game com o Vanguard rodando🤖 Generated with Claude Code