Voltar ao Blog

Marathon Input Issues Fix: Arquitetando jogos de PC para sobreviver a conflitos de Overlay

Publicado em 28 de fevereiro de 2026
Marathon Input Issues Fix: Arquitetando jogos de PC para sobreviver a conflitos de Overlay

Todo desenvolvedor de multiplayer competitivo conhece a sensação de ver um netcode perfeitamente arquitetado quebrar por causa de um overlay do Discord. Você passa meses otimizando sua infraestrutura de servidor para latência sub-30ms, apenas para descobrir que os jogadores estão errando tiros porque um aplicativo de streaming em segundo plano está sufocando a input thread do jogo.

Este cenário exato está ocorrendo no Server Slam de Marathon, o próximo extraction shooter da Bungie. Jogadores de PC relatam mouse lag severo, perda de inputs e movimentos de câmera lentos. A Bungie reconheceu oficialmente o problema, apontando utilitários de terceiros, overlays de streamers e métodos específicos de captura de janela como os principais culpados.

Nos fóruns, milhares de jogadores buscam desesperadamente por um marathon input issues fix. A solução temporária para o jogador é simples: transmitir o monitor inteiro em vez de capturar a janela do jogo e desativar overlays.

Mas para desenvolvedores, isso levanta uma questão arquitetural: por que apps de streaming sequestram os inputs e como podemos projetar nossos clientes de PC para evitar isso?

Neste deep dive técnico, analisaremos o input handling do Windows, por que o OBS Game Capture causa problemas de frame pacing e como desacoplar suas input threads.

A Causa Raiz: Como Overlays sequestram o Render Pipeline

Para entender por que o streaming arruína o mouse input, devemos olhar como os jogos de PC chegam à tela.

Quando um jogador usa OBS ou Discord, esses apps injetam uma DLL diretamente no processo do jogo. Esta DLL intercepta chamadas de API gráfica, fazendo hooking na função Present do DirectX ou vkQueuePresentKHR no Vulkan.

O Gargalo da Swap Chain

Ao fazer hook na swap chain, o overlay força o engine a esperar enquanto copia o frame buffer para seu próprio espaço de memória. Se a CPU ou GPU estiver carregada pelo encoder, essa operação trava. Um frame de 16.6ms (60 FPS) passa a levar 24ms. Como a maioria dos engines consulta o input padrão do Windows (WM_MOUSEMOVE) na main thread antes do render tick, atrasar o render tick atrasa o input polling.

Seu mouse de 1000Hz passa a ser consultado em intervalos erráticos, resultando em uma mira pesada ou flutuante.

Fullscreen vs. Windowed Capture

A Bungie notou que os problemas ocorrem principalmente na captura de janela. Isso se deve ao Windows Desktop Window Manager (DWM).

No Exclusive Fullscreen ou usando o DXGI Flip Model, o jogo ignora a composição do DWM. Mas se um app de streaming força um hook de captura de janela, ele muitas vezes força o jogo de volta ao estado composited, adicionando de 1 a 3 frames de latência (16ms a 48ms) de input delay.

Arquitetando Input Imbatível: Raw Input API

Para imunizar seu jogo, você deve abandonar os message pumps padrão do Windows para inputs críticos. Confiar em WM_MOUSEMOVE é fatal para shooters competitivos.

Implemente a Raw Input API (WM_INPUT) e mova o input polling para uma thread dedicada de alta prioridade, desacoplada do render loop.

Implementando Raw Input em C++ (Win32)

Como ignorar as filas padrão para registrar dados raw do mouse:

// Inicialização Raw Input C++ Win32
void InitializeRawInput(HWND hwnd) {
    RAWINPUTDEVICE Rid[1];
    Rid[0].usUsagePage = 0x01; 
    Rid[0].usUsage = 0x02; 
    Rid[0].dwFlags = 0;   
    Rid[0].hwndTarget = hwnd;

    if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) {
        DWORD error = GetLastError();
        LogWarning("Falha ao registrar raw input: %d", error);
    }
}

// No WndProc
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_INPUT: {
            UINT dwSize = 0;
            GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
            if (dwSize == 0) break;
            LPBYTE lpb = new BYTE[dwSize];
            GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
            RAWINPUT* raw = (RAWINPUT*)lpb;
            if (raw->header.dwType == RIM_TYPEMOUSE) {
                int deltaX = raw->data.mouse.lLastX;
                int deltaY = raw->data.mouse.lLastY;
                InputManager::GetInstance().AccumulateMouseDelta(deltaX, deltaY);
            }
            delete[] lpb;
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Unreal Engine: Desativar Smoothing

A Unreal Engine gerencia Raw Input internamente, mas aplica suavização que piora o lag. Desative no PlayerController:

void ACompetitivePlayerController::SetupInputComponent() {
    Super::SetupInputComponent();
    bEnableMouseSmoothing = false;
    LocalPlayer->AspectRatioAxisConstraint = AspectRatio_MaintainYFOV;
}

Efeito Cascata: Client Lag vs. Server Rollback

O input lag afeta a arquitetura de servidor autoritativo. Se um overlay trava o loop do cliente, os pacotes de input chegam tarde. O servidor precisa acelerar a simulação e, se o atraso for grande, a reconciliation força uma correção (rubberbanding), como visto em How To Fix Player Location Desync In Uefn And Unreal Engine Multiplayer.

Tratamento no Servidor

O servidor deve usar um buffer de input para absorver o jitter.

void ServerProcessClientInputs(ClientConnection* client) {
    const int TARGET_BUFFER_SIZE = 2; 
    int currentBufferSize = client->InputQueue.Size();
    if (currentBufferSize > TARGET_BUFFER_SIZE + 3) {
        client->InputQueue.DropOldest(currentBufferSize - TARGET_BUFFER_SIZE);
    }
    if (!client->InputQueue.IsEmpty()) {
        InputCommand cmd = client->InputQueue.Pop();
        SimulatePlayerMovement(client->PlayerEntity, cmd);
    }
}

Com horizOn, você implanta servidores otimizados com proteção contra desync, focando no client loop.

5 Melhores Práticas para Input no PC

  1. Desacoplar Input do Rendering: Use uma thread de alta prioridade a 1000Hz.
  2. Forçar DXGI Flip Model: Use DXGI_SWAP_EFFECT_FLIP_DISCARD para ignorar o DWM.
  3. Hardware Cursor: Use SetCursor do SO em vez de quads texturizados.
  4. Avisar sobre Hook Injections: Detecte DLLs de overlays e avise o jogador.
  5. Fixed Timestep Simulation: Vincule física e input a um passo de tempo fixo.

Conclusão

O caso Marathon mostra quão hostil o ecossistema de PC pode ser. Enquanto jogadores buscam um marathon input issues fix, desenvolvedores devem implementar Raw Input e desacoplar threads. Para o backend, use horizOn para gerenciar orquestração e desync.