Retour au Blog

Le Steam FPS Predictor arrive : Architecturer la télémétrie matérielle

Publié le 6 avril 2026
Le Steam FPS Predictor arrive : Architecturer la télémétrie matérielle

Tout développeur indépendant connaît cette sensation de vide en voyant tomber une évaluation Steam, non pas parce que le core gameplay loop a échoué, mais parce que le joueur a essayé de faire tourner un rendering pipeline de 2026 sur un GPU intégré de 2014. La demande de remboursement cite inévitablement une "poor optimization, unplayable".

Le coût réel d'une mauvaise performance n'est pas seulement la vente perdue de 19,99 $. C'est le dommage algorithmique infligé à votre page de magasin. L'algorithme de visibilité de Steam punit impitoyablement les jeux avec des taux de remboursement élevés et des agrégats d'évaluations "Mixed" ou "Mostly Negative". Une vague de joueurs tentant de faire fonctionner votre jeu sur un matériel non pris en charge peut enterrer votre titre dans la Discovery Queue de façon permanente.

Bientôt, Valve va changer entièrement cette dynamique. Des fouilles de données récentes du client Steam révèlent qu'une fonctionnalité de performance prédictive est actuellement en développement. Cet outil indiquera ostensiblement aux joueurs le nombre de frames per second (FPS) auxquels ils peuvent s'attendre dans votre jeu avant même qu'ils ne cliquent sur le bouton d'achat.

Il s'agit d'un changement sismique pour la distribution de jeux PC. Il lève l'ambiguïté des "Configurations minimales requises" et la remplace par des données brutes et froides. Si votre jeu est mal optimisé, ou s'il fonctionne terriblement sur les configurations matérielles les plus courantes, Steam va diffuser ce fait directement sur votre page de magasin. Le fardeau de la sensibilisation au matériel se déplace, et les développeurs qui ne collectent pas et n'agissent pas proactivement sur la Performance Telemetry vont voir leurs taux de conversion s'effondrer.

Dissecting the Steam FPS Predictor Leak

La mécanique sous-jacente de cette fonctionnalité à venir, telle que découverte par SteamDB et Lambda Generation, pointe vers une agrégation massive de données de joueurs. Valve mène son Hardware & Software Survey depuis plus de deux décennies. Ils savent précisément quels CPUs, GPUs et configurations de mémoire sont activement utilisés à travers le monde.

Cependant, les enquêtes matérielles statiques ne racontent que la moitié de l'histoire. L'outil de prédiction nécessite un performance profiling actif. Lorsqu'un utilisateur joue à votre jeu, l'overlay de Steam est déjà capable de surveiller les framerates. En corrélant cette Telemetry en direct avec le profil matériel spécifique de l'utilisateur, Valve peut construire une matrice prédictive pour chaque titre sur la plateforme.

Le code divulgué suggère une interface de configuration manuelle où les utilisateurs peuvent saisir différentes spécifications matérielles pour calculer les performances attendues. Plus important encore, il permet aux utilisateurs d'"enregistrer" la configuration de leur machine pour voir instantanément les framerates attendus sur l'ensemble du magasin.

Pour les développeurs, cela signifie que la boîte noire de la performance des joueurs est en train d'être ouverte. Vous ne pouvez plus compter sur des bandes-annonces pré-rendues ou des vertical slices hautement optimisées pour stimuler les ventes si l'exécutable réel tourne à 24 FPS sur une RTX 3060. L'algorithme vous dénoncera.

The Analytics Challenge: Why Performance Prediction is Hard

Prédire les performances d'un jeu est notoirement difficile car le matériel ne s'adapte pas de manière linéaire et les bottlenecks dépendent entièrement du contexte. Un GPU peut facilement atteindre 120 FPS dans un environnement intérieur clos, mais dès que le joueur entre dans un monde ouvert vaste avec une lourde AI simulation, le CPU devient un bottleneck pour le render thread, et les framerates chutent.

De plus, les benchmarks synthétiques reflètent rarement la réalité d'un écosystème PC fragmenté, en proie au thermal throttling, à des pilotes obsolètes et à des processus d'arrière-plan dévorant la RAM du système. C'est pourquoi le suivi d'un simple "Average FPS" est un piège dangereux. Une moyenne de 60 FPS semble parfaitement jouable, mais si cette moyenne est composée de pics à 120 FPS et de chutes fréquentes à 15 FPS pendant les combats, l'expérience du joueur est fondamentalement brisée.

Ces micro-saccades — souvent appelées 1% et 0.1% lows — sont les véritables tueurs du game feel. Si l'outil prédictif de Steam s'appuie sur des moyennes agrégées, il pourrait en fait donner une image erronée de la stabilité de votre jeu. Il est donc absolument critique pour vous, en tant que développeur, d'avoir votre propre Source of Truth.

Vous devez collecter votre propre Hardware Telemetry pour identifier et corriger ces micro-saccades avant que l'algorithme de Steam ne signale votre jeu comme un titre peu performant. Se fier aux rapports Discord de la communauté pour le profilage des performances est une recette pour le désastre.

Architecting Your Own Hardware Telemetry Pipeline in Godot 4

Pour garder une longueur d'avance sur le suivi des performances au niveau de la plateforme, vous devez intégrer un performance profiling automatisé directement dans votre client de jeu. On ne peut pas optimiser ce qu'on ne mesure pas.

L'objectif est de collecter passivement des métriques de performance pendant le jeu réel et de renvoyer ces données à vos serveurs en même temps que les spécifications matérielles du joueur. Cela vous permet de construire votre propre matrice de performance attendue et d'identifier exactement quelles combinaisons CPU/GPU sont à la traîne.

Voici comment vous pouvez construire un hardware profiler complet dans Godot 4. Ce script enregistre les temps de frame sur une durée déterminée et calcule les cruciaux 1% lows qui définissent les saccades perçues.

# Godot 4.x - Comprehensive Hardware Telemetry Profiler
extends Node

var _frame_times: PackedFloat64Array = []
var _is_profiling: bool = false
var _profile_timer: float = 0.0
const PROFILE_DURATION: float = 120.0 # Profile a 2-minute slice of gameplay

func start_profiling() -> void:
    _frame_times.clear()
    _is_profiling = true
    _profile_timer = 0.0

func _process(delta: float) -> void:
    if not _is_profiling:
        return
        
    # Record delta time in milliseconds
    _frame_times.append(delta * 1000.0)
    _profile_timer += delta
    
    if _profile_timer >= PROFILE_DURATION:
        _finish_profiling()

func _finish_profiling() -> void:
    _is_profiling = false
    
    if _frame_times.is_empty():
        return
        
    # Sort the array to calculate percentiles (1% lows)
    _frame_times.sort()
    
    var total_time: float = 0.0
    for time in _frame_times:
        total_time += time
        
    var avg_time: float = total_time / _frame_times.size()
    
    # Calculate the 99th percentile of frame times (the longest frames)
    # This represents the 1% lows
    var one_percent_idx: int = int(_frame_times.size() * 0.99)
    one_percent_idx = clampi(one_percent_idx, 0, _frame_times.size() - 1)
    var one_percent_time: float = _frame_times[one_percent_idx]
    
    # Convert timings back to FPS for the final payload
    var telemetry_payload = {
        "event_type": "performance_profile",
        "client_version": ProjectSettings.get_setting("application/config/version"),
        "hardware": _get_hardware_specs(),
        "performance": {
            "avg_fps": 1000.0 / avg_time,
            "one_percent_low_fps": 1000.0 / one_percent_time,
            "total_frames_analyzed": _frame_times.size()
        }
    }
    
    _transmit_telemetry(telemetry_payload)

func _get_hardware_specs() -> Dictionary:
    return {
        "os": OS.get_name(),
        "cpu": OS.get_processor_name(),
        "gpu": RenderingServer.get_video_adapter_name(),
        "ram_mb": OS.get_memory_info().get("physical", 0) / (1024 * 1024)
    }

func _transmit_telemetry(payload: Dictionary) -> void:
    # Serialize and transmit to your analytics backend
    var json_string = JSON.stringify(payload)
    print("Telemetry Ready: ", json_string)
    # HTTP Request implementation omitted

Ce script Godot réalise deux choses critiques. Premièrement, il évite complètement de bloquer le thread principal pendant la collecte des données. Deuxièmement, il trie le tableau localement pour extraire les percentiles avant la transmission, plutôt que d'envoyer un tableau massif de floats bruts sur le réseau.

Building a Thread-Safe Profiler in Unreal Engine C++

Pour les développeurs utilisant Unreal Engine, les principes restent les mêmes, mais l'implémentation nécessite une gestion rigoureuse de la mémoire pour éviter de causer les saccades que vous essayez de mesurer. L'utilisation d'un GameInstanceSubsystem garantit que votre profiler persiste à travers les chargements de niveau.

Il est crucial de réserver la mémoire pour votre tableau à l'avance. Réallouer un tableau des milliers de fois par seconde pendant le jeu anéantira votre frametime CPU.

// Unreal Engine C++ - Hardware Telemetry Subsystem
// PerformanceTrackerSubsystem.h
#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "PerformanceTrackerSubsystem.generated.h"

UCLASS()
class YOURGAME_API UPerformanceTrackerSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
    GENERATED_BODY()

public:
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    
    // FTickableGameObject interface
    virtual void Tick(float DeltaTime) override;
    virtual TStatId GetStatId() const override;
    virtual bool IsTickable() const override { return bIsTracking; }

    UFUNCTION(BlueprintCallable, Category = "Analytics")
    void StartPerformanceTracking(float DurationInSeconds);

private:
    void ConcludeTrackingSession();
    FString GetHardwareProfileJSON() const;
    void TransmitPayload(const FString& Payload);

    bool bIsTracking = false;
    float TrackingDuration = 0.0f;
    float TimeElapsed = 0.0f;
    
    TArray<float> FrameTimeHistory;
};
// PerformanceTrackerSubsystem.cpp
#include "PerformanceTrackerSubsystem.h"
#include "GenericPlatform/GenericPlatformDriver.h"
#include "GenericPlatform/GenericPlatformMemory.h"
#include "Kismet/GameplayStatics.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"

void UPerformanceTrackerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    FrameTimeHistory.Reserve(10000); // Prevent array reallocation during tracking
}

void UPerformanceTrackerSubsystem::Deinitialize()
{
    Super::Deinitialize();
}

void UPerformanceTrackerSubsystem::StartPerformanceTracking(float DurationInSeconds)
{
    FrameTimeHistory.Reset();
    TrackingDuration = DurationInSeconds;
    TimeElapsed = 0.0f;
    bIsTracking = true;
}

void UPerformanceTrackerSubsystem::Tick(float DeltaTime)
{
    if (!bIsTracking) return;

    // Store frame time in milliseconds
    FrameTimeHistory.Add(DeltaTime * 1000.0f);
    TimeElapsed += DeltaTime;

    if (TimeElapsed >= TrackingDuration)
    {        ConcludeTrackingSession();
    }
}

void UPerformanceTrackerSubsystem::ConcludeTrackingSession()
{
    bIsTracking = false;

    if (FrameTimeHistory.Num() == 0) return;

    // Sort to calculate 1% and 0.1% lows
    FrameTimeHistory.Sort();

    double TotalTime = 0.0;
    for (float FrameTime : FrameTimeHistory)
    {        TotalTime += FrameTime;
    }

    float AverageFrameTime = TotalTime / FrameTimeHistory.Num();
    
    // Calculate Percentiles
    int32 OnePercentIndex = FMath::Clamp(FMath::FloorToInt(FrameTimeHistory.Num() * 0.99f), 0, FrameTimeHistory.Num() - 1);
    int32 PointOnePercentIndex = FMath::Clamp(FMath::FloorToInt(FrameTimeHistory.Num() * 0.999f), 0, FrameTimeHistory.Num() - 1);

    float OnePercentLow = FrameTimeHistory[OnePercentIndex];
    float PointOnePercentLow = FrameTimeHistory[PointOnePercentIndex];

    // Construct JSON Payload
    FString Payload = FString::Printf(TEXT(
        "{\"average_fps\": %.2f, \"1_percent_low_fps\": %.2f, \"0_1_percent_low_fps\": %.2f, \"hardware\": %s}"),
        1000.0f / AverageFrameTime,
        1000.0f / OnePercentLow,
        1000.0f / PointOnePercentLow,
        *GetHardwareProfileJSON()
    );

    TransmitPayload(Payload);
}

FString UPerformanceTrackerSubsystem::GetHardwareProfileJSON() const
{
    FString OSVersion = FPlatformMisc::GetOSVersion();
    FString CPUBrand = FPlatformMisc::GetCPUBrand();
    FString GPUBrand = FPlatformMisc::GetPrimaryGPUBrand();
    
    const FPlatformMemoryConstants& MemoryConstants = FPlatformMemory::GetConstants();
    uint32 TotalPhysicalRAM_GB = MemoryConstants.TotalPhysical / (1024 * 1024 * 1024);

    return FString::Printf(TEXT("{\"os\": \"%s\", \"cpu\": \"%s\", \"gpu\": \"%s\", \"ram_gb\": %d}"),
        *OSVersion, *CPUBrand, *GPUBrand, TotalPhysicalRAM_GB);
}

void UPerformanceTrackerSubsystem::TransmitPayload(const FString& Payload)
{
    // Ensure async HTTP transmission to avoid hitches
    FHttpModule* Http = &FHttpModule::Get();
    TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
    
    Request->SetURL("https://api.yourbackend.com/v1/telemetry/performance");
    Request->SetVerb("POST");
    Request->SetHeader("Content-Type", "application/json");
    Request->SetContentAsString(Payload);
    
    Request->ProcessRequest();
}

TStatId UPerformanceTrackerSubsystem::GetStatId() const
{
    RETURN_QUICK_DECLARE_CYCLE_STAT(UPerformanceTrackerSubsystem, STATGROUP_Tickables);
}

Deep Dive: Structuring Telemetry for Scale

L'écriture du code côté client n'est que la première étape. Le véritable défi technique réside dans l'ingestion et l'interrogation sécurisées de ces données.

Si votre jeu connaît un succès commercial, vous aurez des dizaines de milliers de clients tentant d'envoyer ces payloads JSON simultanément. Si vos clients envoient des données toutes les quelques minutes, une API REST standard s'appuyant sur une base de données relationnelle unique pliera sous les limites de connexion.

Lors de l'architecture du point de terminaison d'ingestion, vous devez utiliser une time-series database optimisée pour un débit d'écriture élevé, couplée à une file d'attente en mémoire (comme Redis) pour tamponner les requêtes HTTP entrantes. Passer à des connexions persistantes peut réduire considérablement la charge, une stratégie que nous avons décrite dans notre Unreal Engine WebSockets tutorial for real-time backends.

The Backend Ingestion Bottleneck

La mise en place de l'infrastructure pour ingérer et stocker des millions de ces payloads de Telemetry nécessite une bande passante d'ingénierie importante. Vous devez configurer des load balancers et gérer le database sharding.

Pour une petite équipe indie, c'est facilement 4 à 6 semaines de travail Backend dédié. Avec horizOn, ces services sont pré-configurés. Vous pouvez acheminer votre Telemetry directement vers un pipeline d'ingestion évolutif qui analyse vos payloads JSON et les rend consultables instantanément.

Best Practices for Hardware Profiling & Performance Tuning

  1. Implémentez l'Auto-Detect matériel au premier démarrage.
  2. Suivez les 1% et 0.1% lows, pas seulement les moyennes.
  3. Pré-allouez votre mémoire de profiling.
  4. Segmentez la Telemetry par Graphics Preset.
  5. Découplez la Telemetry du Main Game Loop.

The Era of Radical Transparency

La décision de Valve d'exposer les données prédictives de FPS est une épée à double tranchant. Pour les développeurs qui privilégient l'optimisation, c'est un outil marketing puissant. Un FPS attendu élevé est un gage de qualité.

La seule façon de survivre à ce changement est de traiter la Performance Telemetry comme une fonctionnalité de base. Commencez à construire vos pipelines maintenant. Analysez vos temps de frame et assurez-vous que l'algorithme confirme ce que vous avez promis : une expérience fluide. Prêt à faire évoluer votre Backend sans maux de tête DevOps ? Essayez horizOn gratuitement et suivez vos 1% lows dès aujourd'hui.


Source: Steam could soon start telling you how many FPS you can expect in games before buying them