1 points par GN⁺ 2024-06-14 | 1 commentaires | Partager sur WhatsApp
  • Serious Engine 1 repose le solo, le multijoueur et la lecture de démos sur une même simulation déterministe, en enregistrant et transmettant à chaque tick les actions des joueurs et des blocs de flux du jeu plutôt que l’état complet
  • Les démos et le multijoueur partagent un état initial de la partie puis appliquent des deltas d’entrée comme CPlayerAction, ce qui fait du déterminisme des graines aléatoires et de la logique de jeu la clé de la synchronisation
  • La couche réseau gère directement au-dessus d’UDP les numéros de séquence, ACK, retransmissions, limitations de bande passante et buffers par connexion afin de maintenir le jeu jouable même sur des lignes lentes
  • CNetworkMessage fournit la sérialisation, la compression LZ77/LZRW1 et un encodage delta basé sur XOR, mais les messages, y compris le chat, ne sont pas chiffrés et peuvent n’être que compressés
  • Le modèle client-serveur de Serious Sam économise de la bande passante en laissant chaque client conserver sa propre simulation, au prix de mécanismes auxiliaires comme la vérification de synchronisation, la prédiction, la retransmission et les contrôles CRC

Structure de simulation commune de Serious Engine

  • Le code source de Serious Engine 1 a été publié en 2016 sous GNU GPL v2, et l’analyse repose sur la lecture et le débogage de cette base de code publique
  • Serious Sam a été conçu dès le départ comme un jeu multijoueur, et même la campagne solo fonctionne en interne comme un cas particulier de cette architecture multijoueur
  • Les modes pris en charge par le moteur sont les suivants
    • Campagne solo hors ligne
    • Coop locale, en LAN, en ligne, ainsi que plusieurs modes de jeu
    • Écran partagé avec plusieurs joueurs sur un même client
    • Enregistrement et lecture de démos

Enregistrement des démos : enregistrer les actions plutôt que l’état complet

  • Enregistrer l’état complet du jeu à chaque tick produirait de gros fichiers, donc Serious Engine sauvegarde une fois l’état complet initial au début de l’enregistrement puis enregistre à chaque tick des blocs de flux du jeu
  • Les blocs de flux du jeu contiennent les types de messages suivants
    • MSG_SEQ_ALLACTIONS : actions des joueurs
    • MSG_SEQ_ADDPLAYER : ajout d’un joueur
    • MSG_SEQ_REMPLAYER : retrait d’un joueur
    • MSG_SEQ_PAUSE : pause ou reprise
    • MSG_SEQ_CHARACTERCHANGE : modification des attributs du personnage du joueur
  • L’élément central est MSG_SEQ_ALLACTIONS ; le moteur désérialise un objet CPlayerAction pour chaque joueur actif et l’applique à CPlayerTarget
  • CPlayerAction contient l’état du joueur
    • vitesse de déplacement en espace monde pa_vTranslation
    • rotation du personnage en espace monde pa_aRotation
    • rotation de la vue en espace monde pa_aViewRotation
    • boutons actuellement pressés pa_ulButtons
    • horodatage en millisecondes basé sur le TSC pa_llCreated
  • Lors de la lecture, l’état initial du jeu est chargé puis les actions des joueurs de chaque tick sont appliquées comme dans une vraie partie

Pourquoi le déterminisme est nécessaire

  • Cette architecture part du principe que tout dans le jeu est entièrement prévisible et que seules les actions des joueurs modifient le jeu
  • Les nombres aléatoires sont eux aussi gérés via un générateur pseudo-aléatoire utilisant une graine faisant partie de l’état du jeu
    • CEntity::IRnd() utilise CSessionState::Rnd()
    • ses_ulRandomSeed est initialisé lors de la désérialisation de l’état du jeu
  • Utiliser un vrai générateur aléatoire ou des graines différentes peut produire des résultats divergents à la lecture d’une même démo, ce qui mène à une désynchronisation

Virgule flottante et traitement par ticks

  • La version PC de Serious Sam étant initialement sortie uniquement sur Windows, l’hypothèse d’un même compilateur et d’une même runtime limitait les problèmes de synchronisation en virgule flottante
  • Le renderer est une DLL et les appels OpenGL ou DirectX peuvent modifier la précision de la FPU ; Serious Engine utilise donc des gardes de précision comme CSetFPUPrecision FPUPrecision(FPT_24BIT)
  • Aucun endroit ne semble définir explicitement le mode d’arrondi, mais un assert vérifie l’état _RC_NEAR via _controlfp
  • La logique de jeu est découplée du framerate de rendu
    • le rendu varie selon le matériel et les réglages, avec apparemment une limite interne de 500 FPS
    • la logique de jeu est fixe à 20 ticks par seconde
  • La fluidité du mouvement est obtenue par interpolation linéaire entre le tick courant et le précédent ; on peut la désactiver via /net_bLerping=0 dans la console

Une couche paquet maison au-dessus d’UDP

  • Le multijoueur de Serious Engine contient encore des noms de fonctions comme StartPeerToPeer_t, mais le modèle réel est de type client-serveur
  • Le serveur reçoit et traite les messages des clients, puis diffuse les informations pertinentes à tous les clients
  • Comme pour le système de démo, chaque joueur exécute sa propre simulation et fait avancer son état à partir des informations d’action reçues des autres joueurs
  • Le réseau utilise UDP, avec un protocole maison par-dessus pour gérer les pertes et la réorganisation des paquets
  • CPacket gère l’ordre et la fiabilité des paquets
    • pa_ulSequence : numéro de séquence servant à ordonner et éliminer les doublons
    • pa_ubReliable : drapeau de fiabilité
    • pa_ubRetryNumber : suivi du nombre de retransmissions
    • pa_tvSendWhen : utilisé pour l’instant d’envoi prévu et le contrôle de congestion
  • Les paquets fiables attendent un ACK et sont retransmis en son absence
    • le nombre maximal de tentatives est défini par net_iMaxSendRetries, apparemment à 10 par défaut
    • l’intervalle de retransmission est défini par net_fSendRetryWait, apparemment à 0.5f par défaut
  • Les paquets fiables peuvent former un flux découpé en plusieurs paquets
    • premier paquet : UDP_PACKET_RELIABLE_HEAD
    • dernier paquet : UDP_PACKET_RELIABLE_TAIL
    • un paquet fiable unique porte les deux drapeaux
  • Les paquets non fiables ne forment pas de flux, car une perte casserait ce flux

Cycle de vie des connexions et routage des paquets

  • CCommunicationInterface est responsable de la communication au niveau paquet et expose des fonctions d’interface pour serveur, client et broadcast
  • Les interfaces serveur et client supposent que le pair connecté est déjà connu, tandis que l’interface broadcast sert à envoyer et recevoir vers des adresses arbitraires
  • CCommunicationInterface maintient deux buffers maîtres
    • cci_pbMasterInput : désérialise les paquets UDP entrants en CPacket et les stocke
    • cci_pbMasterOutput : sérialise les CPacket à envoyer puis les transmet à l’API socket
  • L’abstraction de communication réellement liée à chaque client est assurée par CClientInterface
    • le serveur possède une interface par joueur dans le tableau cm_aciClients
    • le client communique avec le serveur via cm_ciLocalClient
    • client et serveur utilisent tous deux cm_ciBroadcast pour établir les connexions
  • adr_uwID dans CAddress sert soit d’identifiant unique de client, soit d’indicateur de paquet broadcast
    • si la valeur est '//' ou 0, il s’agit d’un paquet broadcast
    • toute autre valeur correspond à un identifiant de client dans la session

Établissement de connexion et protections de base

  • Pour se connecter à un serveur, le client envoie un paquet broadcast fiable portant le drapeau UDP_PACKET_CONNECT_REQUEST
  • Si un client de même adresse et port est déjà connecté, le serveur ignore la requête
  • Pour un nouveau client, le serveur cherche une interface client libre puis effectue les opérations suivantes
    • génération de l’identifiant unique du client
    • envoi de cet identifiant au client dans un paquet broadcast fiable UDP_PACKET_CONNECT_RESPONSE
  • L’identifiant n’est pas un simple index fixe : il combine une partie d’une valeur de timer et l’index du client
  • Devoir deviner uwID pour usurper un autre joueur réduit la surface d’attaque
  • Si un joueur non connecté envoie un paquet non broadcast, Serious Engine peut afficher un avertissement dans la console

Le solo et les démos comme cas particuliers de connexion locale

  • Le solo et la lecture de démo gardent eux aussi en interne un serveur et un client, mais dans le même processus
  • Comme deux instances d’un même processus n’ont pas besoin de sockets, Client_OpenLocal() relie directement l’interface client locale à l’interface côté serveur
  • Les deux CClientInterface connectées utilisent ExchangeBuffers pour transférer les paquets du buffer de sortie de l’une vers le buffer d’entrée de l’autre
  • Le jeu local peut donc se passer des buffers maîtres d’entrée/sortie et des sockets réseau réels

Couche des messages réseau

  • CNetworkMessage est l’abstraction de message au-dessus des paquets, et peut être lu ou écrit comme un flux
  • Les messages sont sérialisés et désérialisés via Read, Write, ReadBits, WriteBits et les opérateurs <<, >>
  • Ils peuvent aussi contenir des sous-messages et, une fois les données écrites, Shrink permet d’ajuster la taille du buffer au volume réel de données
  • Le buffer de CNetworkMessage est alloué via AllocMemory, qui semble appeler malloc en interne
  • CLinearAllocator existe, mais aucun usage n’a été trouvé ; les buffers de messages sont alloués et réalloués fréquemment

Compression et encodage delta

  • Les messages peuvent être compressés avec le compresseur spécifié ou avec le compresseur par défaut du type de message
  • MESSAGETYPE utilise les 6 bits de poids faible pour le type, et les 2 bits restants pour indiquer la méthode de compression
    • LZ77 CzlibCompressor
    • LZRW1 CLZCompressor
    • non compressé
  • La compression par défaut semble être LZRW1, modifiable via la variable shell net_iCompression
  • CPlayerAction n’est pas envoyé tel quel : le moteur transmet un delta obtenu par XOR entre l’action courante et la précédente
  • Côté réception, le delta est de nouveau XORé avec la dernière action pour reconstituer le CPlayerAction d’origine
  • Ce delta se compresse mieux quand les changements de données sont faibles
    • les touches pressées restent souvent identiques sur plusieurs frames
    • la vitesse et la rotation de vue n’explorent pas de grands écarts sur l’ensemble de l’espace flottant
  • L’effet peut être encore meilleur quand le serveur envoie en une fois les actions de plusieurs joueurs via MSG_SEQ_ALLACTIONS

Chiffrement des messages et chat

  • Les messages de Serious Engine ne sont pas chiffrés
  • Avec net_iCompression=0, si la compression est désactivée, les messages de chat du jeu apparaissent en clair dans la charge utile des paquets UDP
  • En situation réelle, si la compression est activée, un observateur du trafic doit identifier et décompresser le flux LZ, mais les données nécessaires sont bien dans le paquet
  • À l’époque, beaucoup de jeux ne géraient pas le chiffrement, et implémenter des mécanismes comme l’authentification et l’échange de clés aurait ajouté de la complexité
  • Le Web de l’époque était lui aussi majoritairement en HTTP

Couche de session de jeu

  • Malgré son nom, CNetworkLibrary gère la session de jeu, y compris l’état CSessionState
  • CNetworkLibrary hérite du CMessageDispatcher mentionné plus haut
  • Au démarrage du serveur, le moteur suit la procédure suivante
    • initialiser la collecte CRC afin de préparer la vérification que les clients connectés disposent bien des mêmes fichiers que le serveur
    • créer un nouveau CSessionState, le sérialiser et le stocker comme état par défaut ga_pubDefaultState
    • charger l’instance locale du monde
    • initialiser l’interface globale de communication
    • initialiser l’état local de session afin de pouvoir envoyer aux clients, à leur connexion, l’état par défaut et le delta d’état par rapport à l’état courant du serveur
    • terminer la collecte CRC et stocker le résultat dans ga_ulCRC
  • La vérification CRC sert moins à l’anti-triche qu’à une détection précoce des désynchronisations
  • La procédure de connexion d’un client suit le flux suivant
    • initialiser un état de session local vide et l’interface de communication
    • envoyer MSG_REQ_CONNECTREMOTESESSIONSTATE avec la version du build, le nom du mode, le mot de passe du serveur, le nombre de joueurs locaux et CSessionSocketParams
    • recevoir MSG_REP_CONNECTREMOTESESSIONSTATE avec le message, le nom du fichier monde, les drapeaux de difficulté et de mode de jeu, ainsi que les propriétés de session
    • initialiser l’état de jeu de référence
    • envoyer MSG_REQ_STATEDELTA pour demander la différence avec l’état courant du serveur
    • après réception de MSG_REP_STATEDELTA, reconstruire le flux d’état du jeu via un diff inversé
    • initialiser l’état de session local avec CSessionState::Read_t()
    • effectuer le contrôle CRC et couper la connexion en cas de divergence

Boucle principale et retransmission du flux du jeu

  • Les boucles principales du client et du serveur sont globalement similaires, mais le serveur effectue des tâches supplémentaires
  • La boucle met à jour l’interface client locale et l’interface broadcast, puis laisse l’état de session local traiter les messages réseau entrants
  • Le serveur gère aussi l’échange de buffers entre interfaces client appariées, la mise à jour des interfaces client côté serveur, celle de GameAgent et le traitement des commandes shell d’administration distante
  • SessionStateLoop() traite séparément les messages non fiables et fiables
    • non fiables : MSG_GAMESTREAMBLOCKS, MSG_KEEPALIVE, MSG_INF_PINGS, MSG_CHAT_OUT
    • fiables : MSG_INF_DISCONNECTED, MSG_ADMIN_RESPONSE
  • MSG_GAMESTREAMBLOCKS est un message non fiable, mais sa perte peut casser la synchronisation
  • Serious Engine vérifie donc les séquences manquantes lors du traitement du flux du jeu et demande une retransmission si nécessaire
    • si le bloc de séquence attendu suivant est présent, il est traité
    • si le bloc suivant manque et qu’aucun bloc plus récent n’existe, la boucle ne fait rien pour ce tour
    • si le bloc suivant manque mais qu’un bloc plus récent existe, un timeout est défini car une perte est possible
    • après le timeout, MSG_REQUESTGAMESTREAMRESEND demande le numéro de séquence manquant et le nombre de blocs concernés
  • Le serveur renvoie alors les blocs de flux du jeu demandés

Traitement des blocs de flux du jeu

  • MSG_SEQ_ADDPLAYER est envoyé quand un joueur rejoint la partie ; il contient l’index du joueur et un descripteur CPlayerCharacter
  • MSG_SEQ_REMPLAYER est envoyé lors de la déconnexion d’un joueur et ne contient que l’index du joueur
  • MSG_SEQ_CHARACTERCHANGE transmet les changements de nom, d’équipe et d’apparence du joueur
    • dans Serious Sam, le buffer d’apparence contient une structure CPlayerSettings
    • on y trouve le nom de fichier du modèle du joueur, la politique de sélection automatique d’arme, le type de réticule et divers drapeaux
  • MSG_SEQ_PAUSE transmet la mise en pause ou la reprise et affiche dans la console le nom du joueur qui l’a demandée
  • MSG_SEQ_ALLACTIONS contient l’instant du tick courant ainsi que les actions de tous les joueurs
    • chaque CPlayerTarget reçoit un CPlayerAction
    • viennent ensuite le traitement des timers, des événements, des entités mobiles et de la physique
  • Le contrôle de synchronisation est effectué via MakeSynchronisationCheck()
    • un CSyncCheck est créé via ChecksumForSync() pour les entités, cibles joueur, etc.
    • le client envoie MSG_SYNCCHECK au serveur ; si l’état diffère de celui du serveur, la connexion est coupée

La prédiction pour réduire la latence d’entrée

  • La prédiction sert à éviter qu’un jeu rapide paraisse lourd à cause de la latence Internet
  • La prédiction du joueur local se base sur les actions envoyées au serveur
  • La prédiction des joueurs distants se base sur les dernières actions reçues du serveur
  • Si le client fait progresser lui-même l’état réel du jeu sans attendre la réponse du serveur, il ignore les actions des autres joueurs, ce qui peut provoquer une désynchronisation
  • Pour éviter de mélanger état réel et état prédit, Serious Engine utilise des predictors
    • un predictor ressemble à une copie « fantôme » liée à une entité normale
    • un temporary predictor est créé pendant la prédiction et n’a pas d’entité correspondante dans l’état réel du jeu
  • Lors du traitement des ticks prédits, seules les entités predictor sont traitées
  • Quand le client reçoit les actions des joueurs depuis le serveur, il détruit les predictors existants et démarre un nouveau cycle de prédiction
  • Au rendu, l’entité d’origine en cours de prédiction n’est pas dessinée ; c’est le predictor qui l’est, afin de donner l’impression d’un mouvement continu sans trop modifier l’état réel
  • Le joueur local ne peut prédire que jusqu’au nombre d’actions déjà envoyées au serveur et stockées dans plt_abPrediction
  • Pour la prédiction des joueurs distants, si cli_bLerpActions est désactivé, la dernière action reçue est répétée
  • Si cli_bLerpActions est activé, une interpolation linéaire est faite entre les deux dernières actions, mais cette option est désactivée par défaut

Comparaison avec Doom et Quake

  • Le networking de Doom était en pratique pair à pair, mais les clients échangeaient des structures proches de CPlayerAction et exécutaient chacun leur propre simulation indépendante
  • Doom utilisait lui aussi un système similaire pour l’enregistrement et la lecture des démos
  • Quake reposait sur une architecture différente, dans laquelle le client n’exécute pas lui-même une grande partie de la logique du jeu et reçoit plutôt des mises à jour d’état du serveur
  • L’approche de Quake limite les problèmes de synchronisation et peut aussi faciliter l’anti-triche, par exemple en évitant d’envoyer au client les entités situées derrière un mur
  • Dans Serious Sam, il est courant d’avoir bien plus d’ennemis actifs et d’objets qu’avec Quake ; envoyer l’état de nombreux objets à chaque tick aurait donc pu coûter très cher en bande passante

Portabilité et limites structurelles

  • Certains messages réseau sérialisent des structures d’une manière proche d’un reinterpret cast
  • Cela peut fonctionner avec un seul compilateur et une seule plateforme, mais dans un jeu cross-platform, la disposition mémoire et le padding des structures peuvent varier
  • Un exécutable 32 bits peut viser un alignement sur 4 octets, alors qu’un exécutable 64 bits peut tenter un alignement sur 8 octets
  • Des problèmes d’endianness existent aussi
    • les PC x86 sont little-endian
    • la PS3 est big-endian
  • L’architecture de Serious Engine est élégante dans sa façon d’abstraire, côté logique de jeu, les différences entre médias de transport comme le réseau ou les fichiers, mais le modèle où chaque client possède une copie de l’état du jeu ouvre la porte à la triche
  • Par exemple, un client modifié pourrait afficher en deathmatch la silhouette des autres joueurs derrière les murs

1 commentaires

 
GN⁺ 2024-06-14
Commentaires sur Hacker News
  • J’étais l’un des développeurs chargés de l’implémentation du code réseau de Serious Sam.
    Je dormais souvent sous un bureau dans les locaux de Croteam et je fouillais Usenet ; j’avais notamment été inspiré par un article expliquant le système de prédiction de QuakeWorld.
    Cette nuit-là, pendant que mon collègue Dan faisait des tests avec une vieille machine Unix 486 utilisée comme routeur pour simuler la latence, j’ai codé une implémentation minimale très simple.
    C’était bien avant que le vrai jeu soit construit par-dessus.

    • Je me demande pourquoi ils ont créé ces types à tête explosive qui foncent sur vous en hurlant, avec un son qui devient de plus en plus fort à mesure qu’ils approchent.
      Je l’entends encore.
    • J’adore l’ambiance où, sous un article sur la création d’un jeu légendaire, quelqu’un dit nonchalamment : « Ah oui, ça, c’est moi qui l’ai fait, c’était fun. »
    • J’adorais vraiment ce jeu.
      J’aimais beaucoup les jeux coopératifs et les shooters, mais mes amis voulaient toujours jouer uniquement à Counter-Strike.
      Grâce à Serious Sam, j’arrivais parfois à les convaincre de jouer avec moi à un jeu que j’aimais.
    • Serious Sam était l’un des jeux préférés de mon oncle, avec Duke Nukem 3D.
      Mon oncle comptait énormément dans ma vie, et jouer aux jeux qu’il aimait est une belle façon de rester lié à son souvenir.
      Un excellent jeu, un excellent multijoueur, et de très bons souvenirs.
    • Le jeu en écran partagé était vraiment appréciable.
  • Serious Sam a toujours été un excellent jeu de LAN party.
    Ce n’était pas parce que c’était le titre le plus spectaculaire de l’époque, ni parce que quelqu’un l’avait planifié à l’avance.
    Quand les autres jeux mouraient à cause de problèmes de pilotes, de chauffe, de mises à jour, etc., on lançait Serious Sam et ça marchait, tout simplement ; c’est comme ça qu’il dominait les LAN parties.
    Les suites ont conservé cette qualité : même quand le PC de quelqu’un partait complètement en vrille, le jeu gérait de façon fiable l’écran partagé et les périphériques d’entrée.
    Sur le plan système, le jeu était vraiment remarquable en matière de fiabilité.

    • Serious Sam tournait vite même sur du matériel médiocre, tout en restant plutôt joli.
      De la même manière, Counter-Strike n’avait pas des graphismes extraordinaires, mais il tournait bien même sur des PC grille-pain, ce qui l’a rendu populaire très longtemps.
    • Le son « aaaaaaaaaaaaah » qui sortait de plusieurs enceintes était réjouissant.
    • À la fin des années 90, je gérais le site de support technique d’EA, et les équipes support/QA jouaient massivement à Serious Sam après le travail.
      C’était le seul FPS qui tournait régulièrement bien sur les PC de bureau, et il était vraiment amusant.
      À l’époque chez EA, la QA et le support technique se recoupaient beaucoup : les personnes du support servaient de bêta-testeurs internes l’été pour tenir les sorties de fin d’année, puis faisaient du support technique en hiver quand les appels augmentaient vers Noël.
  • Pour implémenter le multijoueur dans le portage Game Boy Color de Vigilante 8, nous utilisions un gameplay déterministe.
    Le câble link de la GBC envoyait et recevait simultanément 1 octet dans chaque sens, et fonctionnait comme une paire de registres à décalage qui se remplissaient mutuellement de part et d’autre du câble.
    Le jeu était calé sur la fréquence d’images de la GBC et, en pratique, il fallait faire beaucoup de travail de mise à jour de l’écran à chaque V-Blank ; si on le ratait, le défilement fluide se cassait.
    Au début du multijoueur, on échangeait les seeds, puis l’exécution se passait ainsi : à l’image A, on lisait les entrées, on les compressait en 1 octet et on les mettait dans le tampon d’envoi. Pendant le rendu de l’image B, la transmission avait lieu. Au début de l’image C, on disposait de l’entrée locale envoyée à l’image A et de l’entrée adverse reçue à l’image B.
    On appliquait ces entrées à l’état du jeu puis on rendait l’image C ; les entrées locales comme distantes étaient donc appliquées avec 1 image de latence.
    En solo local, il n’y avait pas de délai d’entrée ; donc si vous perdiez en multijoueur, vous pouviez accuser la latence, et au besoin m’accuser personnellement.

    • J’ai acheté cette cartouche il y a quelques semaines et, comme j’aime les vieilles cartouches à vibration GBC, j’ai été impressionné de voir qu’il y avait du multijoueur par câble link.
      C’est un très bon jeu, et l’explication technique de l’implémentation du multijoueur est vraiment chouette.
  • Croteam est une équipe de développement de jeux vraiment talentueuse.
    J’ai énormément apprécié The Talos Principle 1 et 2, et avec le premier, ils faisaient partie des pionniers à avoir créé très tôt un moteur de jeu Vulkan entièrement maison.

    • J’ai trouvé très dommage qu’ils abandonnent leur propre moteur dans The Talos Principle 2 pour utiliser Unreal Engine.
    • Je viens d’apprendre que le DLC de Talos 2 sort ce vendredi sur Steam.
  • Je me demande si c’est la même idée que « 1500 archers en 28,8K » pour Age of Empires.
    https://www.gamedeveloper.com/programming/1500-archers-on-a-...

    • Oui. Les deux reposent sur un système de lockstep déterministe.
      Beaucoup de jeux ont longtemps utilisé ce type de système, mais aujourd’hui il semble moins courant qu’avant pour diverses raisons.
    • Ce chiffre montre à quel point il est étrange que Tempest Rising ait une limite d’unités.
      Soit on a les ressources, soit on ne les a pas ; pas besoin d’une limite juste pour limiter.
  • Des jeux disposant de 10 fois plus de bande passante ont du mal à prendre en charge autant d’ennemis.
    Je réalise seulement maintenant que l’augmentation des ressources techniques a plutôt un effet négatif sur l’efficacité et la créativité en informatique.
    Plus la bande passante, le stockage, la mémoire et la puissance de calcul augmentent, plus les logiciels réagissent en devenant plus lents, plus obèses et moins compétents par unité de ressource.
    On pourrait appeler ça l’effet Benjamin Button de la conception logicielle.

    • Je serais curieux de savoir combien représente « autant d’ennemis ».
      S’il y a un chiffre dans l’article, je ne l’ai pas trouvé.
      Parmi les jeux modernes, il y en a beaucoup qui sont multijoueurs et dont le nombre d’ennemis peut raisonnablement être qualifié de « massif » ; et si l’important est le nombre de joueurs, il existe aussi des jeux qui prennent en charge un grand nombre de joueurs.
    • On appelle généralement ça plus simplement le bloat logiciel.
    • Oui, c’est connu sous le nom de loi de Wirth.
  • Je considère que c’est un jeu où l’on passait plus de temps à reculer qu’à avancer.

    • Il y a même un jeu là-dessus, intitulé « I Hate Running Backwards ».
      Il est sur Steam et je ne sais pas si c’est le même studio, mais il appartient à l’univers de Serious Sam.
    • Vous et Netrisca étiez ensemble, mais ces milliers d’ennemis étaient seuls.
    • Il y avait même une arme qui donnait l’impression de tirer une fraction de seconde avant d’appuyer sur le bouton de la souris.
    • Je me souviens aussi d’avoir désespérément ramassé les munitions générées en reculant comme ça.
  • L’architecture de Factorio est assez similaire : elle n’envoie quasiment que des événements d’entrée et s’appuie sur un cœur de simulation en lockstep.
    Il y a quelques exceptions visibles, comme l’outil de planification ferroviaire.

    • J’aimerais un jour travailler sur une architecture lockstep de ce genre.
      Ça ressemble à une contrainte de conception satisfaisante et facile à tester.
  • Je me souviens avoir joué à Serious Sam enfant via une démo de PC Gamer.
    Déjà à l’époque, il était perçu comme un jeu rétro qui revenait aux anciens jours de DOOM et Quake.
    Maintenant, littéralement 20 ans ont passé, et il est lui-même devenu un classique.

  • Starsiege: Tribes était vaste et absurdement fun même avec une connexion 56K.

    • Les développeurs de Tribes ont écrit un livre blanc couvrant des concepts similaires de code réseau.
      https://www.gamedevs.org/uploads/tribes-networking-model.pdf
    • C’est probablement mon jeu préféré de quand j’étais enfant, surtout Tribes 2.
      En fait, j’ai téléchargé Tribes 2 il y a quelque temps et j’y ai encore joué contre des bots il y a quelques mois.
      C’est un vieux jeu, mais il reste amusant, et je pense souvent que j’aimerais le refaire avec quelque chose comme Unity.
      Peut-être un jour.
    • Tribes était excellent.
      En 1999, on dévalait comme à ski des terrains générés procéduralement, on voyait d’autres joueurs remonter dessus de la même manière, avec de grandes cartes et beaucoup de joueurs.