Terug naar Blog

De Steam FPS Predictor komt eraan: Hardware Telemetry architecteren

Gepubliceerd op 6 april 2026
De Steam FPS Predictor komt eraan: Hardware Telemetry architecteren

Elke indie-ontwikkelaar kent het zinkende gevoel wanneer een Steam-review binnenkomt, niet omdat de core gameplay loop faalde, maar omdat de speler probeerde een 2026 rendering pipeline te draaien op een geïntegreerde GPU uit 2014. Het restitutieverzoek citeert onvermijdelijk "poor optimization, unplayable."

De werkelijke kosten van slechte prestaties zijn niet alleen de verloren verkoop van $19,99. Het is de algoritmische schade die aan je winkelpagina wordt toegebracht. Het visibility algorithm van Steam straft games met hoge restitutiepercentages en "Mixed" of "Mostly Negative" review-aggregaten meedogenloos af. Een golf van spelers die probeert je game op niet-ondersteunde hardware te draaien, kan je titel permanent in de Discovery Queue begraven.

Binnenkort gaat Valve deze dynamiek volledig veranderen. Recente datamining van de Steam-client onthult dat er momenteel een functie voor voorspellende prestaties in ontwikkeling is. Deze tool zal spelers zogenaamd vertellen hoeveel frames per second (FPS) ze in je game kunnen verwachten voordat ze zelfs maar op de koopknop klikken.

Dit is een seismische verschuiving voor de distributie van pc-games. Het neemt de dubbelzinnigheid van "Minimale Systeemvereisten" weg en vervangt deze door harde gegevens. Als je game slecht geoptimaliseerd is, of als deze verschrikkelijk draait op de meest voorkomende hardwareconfiguraties, gaat Steam dat feit direct op je winkelpagina uitzenden. De last van hardware-bewustzijn verschuift, en ontwikkelaars die niet proactief Performance Telemetry verzamelen en daarop acteren, zullen hun conversiepercentages zien kelderen.

Dissecting the Steam FPS Predictor Leak

De onderliggende mechanica van deze aankomende functie, zoals ontdekt door SteamDB en Lambda Generation, wijzen op een enorme aggregatie van spelersgegevens. Valve voert al meer dan twee decennia zijn Hardware & Software Survey uit. Ze weten precies welke CPUs, GPUs en geheugenconfiguraties wereldwijd actief worden gebruikt.

Statische hardware-enquêtes vertellen echter maar het halve verhaal. De voorspellingstool vereist actieve performance profiling. Wanneer een gebruiker je game speelt, is de overlay van Steam al in staat om framerates te monitoren. Door deze live Telemetry te correleren met het specifieke hardwareprofiel van de gebruiker, kan Valve een voorspellende matrix bouwen voor elke titel op het platform.

De gelekte code suggereert een handmatige configuratie-interface waar gebruikers verschillende hardwarespecificaties kunnen invoeren om de verwachte prestaties te berekenen. Belangrijker nog is dat gebruikers hun machineconfiguratie kunnen "opslaan" om direct de verwachte framerates in de hele winkel te zien.

Voor ontwikkelaars betekent dit dat de black box van spelerprestaties wordt opengebroken. Je kunt niet langer vertrouwen op pre-rendered trailers of hoog geoptimaliseerde vertical slices om de verkoop te stimuleren als het eigenlijke executable met 24 FPS voortkabbelt op een RTX 3060. Het algoritme zal je ontmaskeren.

The Analytics Challenge: Why Performance Prediction is Hard

Het voorspellen van gameprestaties is notoir moeilijk omdat hardware niet lineair schaalt en bottlenecks volledig contextafhankelijk zijn. Een GPU kan gemakkelijk 120 FPS halen in een afgesloten binnenomgeving, maar zodra de speler een uitgestrekte open wereld met zware AI simulation betreedt, vormt de CPU een bottleneck voor de render thread en storten de framerates in.

Bovendien weerspiegelen synthetische benchmarks zelden de realiteit van een gefragmenteerd pc-ecosysteem dat geteisterd wordt door thermal throttling, verouderde drivers en achtergrondprocessen die systeem-RAM opslokken. Dit is waarom het bijhouden van simpele "Average FPS" een gevaarlijke valstrik is. Een gemiddelde van 60 FPS klinkt perfect speelbaar, maar als dat gemiddelde bestaat uit uitschieters van 120 FPS en frequente dalingen naar 15 FPS tijdens gevechten, is de spelerservaring fundamenteel kapot.

Deze micro-stutters — vaak aangeduid als 1% en 0.1% lows — zijn de echte moordenaars van het gamegevoel. Als de voorspellingstool van Steam vertrouwt op geaggregeerde gemiddelden, kan deze de stabiliteit van je game feitelijk verkeerd weergeven. Dit maakt het absoluut cruciaal voor jou, als ontwikkelaar, om je eigen Source of Truth te hebben.

Je moet je eigen Hardware Telemetry verzamelen om deze micro-stutters te identificeren en te verhelpen voordat het algoritme van Steam je game markeert als een slecht presterende titel. Vertrouwen op community-discordrapporten voor performance profiling is een recept voor rampen.

Architecting Your Own Hardware Telemetry Pipeline in Godot 4

Om platform-tracking voor te blijven, moet je geautomatiseerde performance profiling direct in je game client inbedden. Je kunt niet optimaliseren wat je niet meet.

Het doel is om passief prestatiemetrieken te verzamelen tijdens de werkelijke gameplay en die gegevens terug te sturen naar je servers, samen met de hardwarespecificaties van de speler. Hierdoor kun je je eigen matrix van verwachte prestaties bouwen en precies identificeren welke CPU/GPU-combinaties het moeilijk hebben.

Hier is hoe je een uitgebreide hardware profiler bouwt in Godot 4. Dit script registreert frametijden over een ingestelde duur en berekent de cruciale 1% lows die de waargenomen stutter definiëren.

# 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

Dit Godot-script bereikt twee kritieke zaken. Ten eerste vermijdt het volledig het blokkeren van de main thread tijdens het verzamelen van gegevens. Ten tweede sorteert het de array lokaal om de percentielen te extraheren voordat ze worden verzonden.

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

Voor ontwikkelaars die Unreal Engine gebruiken, blijven de principes hetzelfde, maar de implementatie vereist zorgvuldig geheugenbeheer om precies die stutters te voorkomen die je probeert te meten. Het gebruik van een GameInstanceSubsystem zorgt ervoor dat je profiler blijft bestaan over level loads heen.

Het is cruciaal om vooraf geheugen te reserveren voor je array. Het duizenden keren per seconde heralloceren van een array tijdens het spelen zal je CPU-frametime vernietigen.

// 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

Het schrijven van de client-side code is slechts de eerste stap. De echte uitdaging ligt in het veilig verwerken en opvragen van deze gegevens.

Als je game commercieel succesvol wordt, zul je tienduizenden clients hebben die tegelijkertijd JSON-payloads proberen te verzenden. Een standaard REST API zal bezwijken onder de verbindingslimieten.

Bij het ontwerpen van het ingestion endpoint moet je een time-series database gebruiken die geoptimaliseerd is voor een hoge schrijfdoorvoer, gekoppeld aan een in-memory queue (zoals Redis). Overstappen naar persistente verbindingen kan de overhead drastisch verminderen, een strategie die we hebben beschreven in onze Unreal Engine WebSockets tutorial for real-time backends.

The Backend Ingestion Bottleneck

Het bouwen van de infrastructuur om miljoenen Telemetry-payloads te verwerken vereist aanzienlijke capaciteit. Voor een klein indie-team is dit gemakkelijk 4-6 weken Backend-werk. Met horizOn zijn deze services al geconfigureerd. Je kunt je Telemetry direct naar een schaalbare pipeline sturen die je JSON-payloads analyseert.

Best Practices for Hardware Profiling & Performance Tuning

  1. Implementeer automatische hardware auto-detect bij de eerste start.
  2. Houd 1% en 0.1% lows bij, niet alleen gemiddelden.
  3. Pre-alloceer je profiling-geheugen.
  4. Segmenteer Telemetry op Graphics Preset.
  5. Ontkoppel Telemetry van de main game loop.

The Era of Radical Transparency

De stap van Valve om voorspellende FPS-gegevens te tonen is een tweesnijdend zwaard. Voor ontwikkelaars die optimalisatie prioriteren, is het een krachtige marketingtool.

De enige manier om deze verschuiving te overleven, is door Performance Telemetry als een kernfunctie te behandelen. Begin nu met het bouwen van je pipelines. Analyseer je frametijden en zorg ervoor dat het algoritme bevestigt wat je beloofde: een soepele ervaring. Klaar om je analytics backend te schalen zonder DevOps-hoofdpijn? Probeer horizOn gratis en begin vandaag nog met het tracken van je 1% lows.


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