Voyage dans les fichiers d'un jeu de 2007
The Witcher ne tourne pas sur un moteur maison. En 2003, CD Projekt — alors un petit studio surtout connu pour localiser des jeux en polonais — licencie l'Aurora Engine de BioWare, celui de Neverwinter Nights, et passe quatre ans à le transformer.
Le moteur qui en résulte — Aurora réécrit à environ 80 % — garde le squelette d'origine : son format d'archive KEY/BIF, son langage de script NWScript. Par-dessus, le studio greffe un nouveau rendu, la physique Havok et une couche Lua complète pour l'interface et les mini-jeux.
Compilé le 21 avril 2009 pour l'Enhanced Edition, le jeu empaquette 34 110 ressources dans 19 archives — près de six gigaoctets. Pour comprendre comment il est construit, il faut d'abord savoir comment il range ses fichiers.
Le format d'index a été modifié au passage : chaque entrée du fichier KEY occupe 26 octets là où BioWare en utilisait 22. C'est ce décalage qu'il a fallu retrouver avant de pouvoir ouvrir la moindre archive.
Pour piloter son monde, The Witcher s'appuie sur deux langages à la fois.
NWScript, hérité de Neverwinter Nights, gère la logique de jeu : intelligence artificielle, quêtes, dialogues, apparition des créatures. Lua prend en charge tout ce qui touche à l'interface et au temps réel — menus, combat, animations, mini-jeux.
Une grande partie de cette logique porte une signature. Les initiales as> ouvrent des centaines de commentaires : ceux d'Arkadiusz Sito, à qui l'on doit notamment le système d'IA — 175 Ko de code à lui seul.
À droite, le code tel qu'il sort des archives. Passez d'un onglet à l'autre pour comparer les deux langages et l'API du moteur.
// File: inc_ai.nss // Author: Arkadiusz Sito // Copyright (c) CD Projekt Red Studio void _ResetModifiers() { object oPlayer = GetFirstPC(); _SetModCurrentTarget(-2, oPlayer); _SetModTargetIsPC(-10, oPlayer); _SetModCloser(-3, oPlayer); _SetModGuardian(-20, oPlayer); _SetModDefensive(-20, oPlayer); _SetModPCAttacker(-60, oPlayer); _SetModMaxAttackers(5, oPlayer); _SetModMaxAttackersAmount(4, oPlayer); }
Les profils de comportement, eux, sont commentés en polonais :
// tw> Sprawdza, jaki behaviour ma ustawiona postac int GetBehaviourType(object oSource); // as> Zeruje wszystkie profile zachowan u postaci void ClearBehaviours(object oSource); // as> Ustawia konkretny typ zachowania void SetBehaviour(int nBehaviour, object oSource); // as> Dodaje postac do druzyny obiektu oLeader void AddToParty(object oLeader, object oSource); // as> Okresla nowy cel ataku z uwzgednieniem zasad podzialu przeciwnikow object DetermineNewTarget(object oIntruder, object oSource);
Le fichier source du mini-jeu, avec ses notes de développement en polonais :
--[[ TODO: BUGZORY: - w momencie kiedy zapauzuje gre przed odpaleniem minigry a ona sie zalaczy to nie da sie odpauzowc bo spacja nie jest handlowana :) → « quand on met pause avant de lancer le mini-jeu et qu'il se lance quand même, on ne peut plus enlever la pause car espace n'est pas géré :) » DTODO11 : bug - ai rzuca kostkami 2 razy :) → « bug — l'IA lance les dés 2 fois :) » ]]
-- Attaque de la goule (m0_ghoul_att) DefAttack { WeaponType = "Monster", Level = "Basic", Name = "m0_ghoul_att", NumberOfHits = 1, Damage = { Medium = "Attack", Min = 20, Max = 34 }, Alternates = { { -- variante : saignement DefenderEffects = { { Type = "Bleeding", BaseIntensity = 110 }, } }, } }
4 252 lignes de définitions. Au-delà des fonctions Aurora d'origine, le studio en a ajouté des dizaines, propres au Witcher :
// Adds current grease ability to item. U can remove it with ClearGrease. // - sAbility - nazwa identyfikujaca zdolnosc // - nMinutes - czas trwania w minutach swiata gry. 0 - ability nie konczy sie. int AddGreaseAbility(string sAbility, object oItem, int nMinutes = 0); void ClearGrease(object oItem); // tw> wejscie w fistfight void EnterFistfightMode(object oCreature); // as> Finds the best waypoint to hideout of rain. object GetBestRainHideout(object oSource); // as> Internal function. void _DespawnBecouseOfRain(object oSource); // ^ « Becouse » — la faute est restée dans le binaire
Les initiales des développeurs se lisent dans les commentaires. Les rôles ci-dessous sont déduits des fonctions que chacun a annotées, non d'un générique officiel :
Arkadiusz Sito — IA, combat, perception
Tomasz W. — moteur, dialogues, physique
UI, système de corruption
Avant d'être une silhouette dans le brouillard, chaque monstre est une fiche de chiffres dans un fichier Lua.
Vitalité, attaque, esquive, parade ; ce qu'il craint, ce qui ne lui fait rien. Ces valeurs, lues telles quelles dans le code, suffisent à expliquer pourquoi un noyeur tombe sous l'argent et encaisse l'acier sans broncher.
Treize créatures, leurs statistiques exactes. Choisissez un nom.
Tout ce qui bouge à l'écran — créature, personnage, arme — part d'un même objet : un maillage 3D rangé dans les archives meshes00.bif.
Le moteur hérite du format binaire d'Aurora : la géométrie est stockée en .mdb, enroulée autour d'une texture dépliée en UV, puis greffée sur un squelette qui porte les animations. Le même squelette sert plusieurs modèles — c'est ce qui permet à deux noyeurs de partager une démarche.
Les poids d'attache de chaque sommet à son os n'étaient documentés nulle part : il a fallu les reconstituer pour redonner vie au maillage. Voici quatre modèles décodés en glTF, rejoués dans le navigateur avec l'une de leurs animations d'origine.
Choisissez un nom à droite. Glissez sur la scène pour tourner autour du modèle.
Une conversation, dans The Witcher, n'est pas du texte : c'est un arbre.
Chaque fichier .dlg, au format GFF de BioWare, enchaîne des répliques de PNJ et des réponses du joueur, reliées par des index. Le jeu a été écrit en polonais, puis doublé en plusieurs langues — la voix change, l'arbre reste le même.
Choisissez la langue, puis suivez l'échange réplique par réplique. Chaque ligne de PNJ peut être jouée dans sa voix d'origine.
Les cartes que Geralt collectionne au fil de ses rencontres ont été peintes à la main, une par une, en 1024×1024.
Elles n'étaient pas rangées avec les autres textures : il a fallu les retrouver dans une archive localisée à part, séparée parce que certaines régions en recevaient des versions différentes.
Choisissez un nom pour l'afficher en grand.
L'acier pour les hommes, l'argent pour les monstres. Cette règle, célèbre dans la saga, n'est pas qu'affaire de lore : c'est un multiplicateur de dégâts.
Chaque créature porte un Steel_Mult et un Silver_Mult. Sur un alp, l'acier ne fait qu'un quart des dégâts quand l'argent en inflige le double.
À côté, le système d'huiles enduit la lame via une simple fonction du moteur, et chaque coup tire au sort entre plusieurs variantes — normale, lourde, étourdissement, mise à terre — chacune avec sa probabilité.
-- Exemple : l'Alp MediumResistance = { Steel_Mult = 0.25, -- l'acier ne fait que 25% des dégâts Silver_Mult = 2.0, -- l'argent fait 200% des dégâts Attack_Mult = 0.25, -- attaques non-épée : 25% }
// Ajoute une huile à une arme. Durée en minutes de jeu. 0 = permanent. int AddGreaseAbility(string sAbility, object oItem, int nMinutes = 0); void ClearGrease(object oItem);
2 coups · 24–30 dégâts chacun
100 % (par défaut)
1 coup · 48–60 dégâts
19 % de chance
1 coup · 48–60 dégâts + Stun (105)
32 % de chance
1 coup · 48–60 dégâts + Knockdown
46 % de chance
| Type | Lueur | Traînée | Sang | Blessures |
|---|---|---|---|---|
| Parade | fx_parry_metal | — | — | Non |
| Normal | fx_sword_g_01 | fx_wpntrail01 | fx_blood_l | Non |
| Critique | fx_sword_g_01 | fx_wpntrail01 | fx_bleeding00 | Oui |
| Finisher | fx_sword_g_01 | fx_wpntrail01 | fx_bleeding01 | Oui + fx |
| Mains nues | — | — | fx_blood_z | Non |
Sous les systèmes, il reste des gens. Le code de The Witcher en garde la trace.
Des commentaires en polonais à côté de l'anglais, des fonctions laissées vides au nom d'un collègue, une faute d'orthographe figée dans l'API, des notes de bug griffonnées entre deux accolades.
Rien de tout cela n'était destiné à être lu. C'est précisément ce qui le rend touchant.
void SlotDlaTomka01(); — « emplacement pour Tomek nº1 »void SlotDlaTomka02();void SlotDlaMichala5(); — « emplacement pour Michał nº5 »
void _DespawnBecouseOfRain(object oSource);int _IsDespawningBecouseOfRain(object oSource);witcher.ini est introuvable au démarrage, le moteur n'affiche ni « Fichier introuvable », ni « Erreur de configuration ». Le titre de l'erreur est : « Serious lack of data » — grave manque de données. Quelqu'un, ce jour-là, s'est fait plaisir.
GetTickCount() passé à srand(). La conséquence : le hasard du jeu est déterministe si l'on lance la partie exactement à la même milliseconde. Avis aux speedrunners.
Le moteur expose des centaines de paramètres de configuration. Quelques-uns trahissent leur auteur.
« Ne touche jamais à mon gamma. » Un autre système écrasait son réglage ; plutôt qu'un ticket, il a ajouté un booléen. Le nom de la variable est le message.
Référence à Queen, Crazy Little Thing Called Love (1979). Ce qu'il contrôle reste un mystère — probablement un effet de rendu.
Exactement ce qu'on imagine : tue d'un coup toute la faune. Pratique pour déboguer une quête sans bestioles dans les pattes.
Mode dieu. Un seul booléen, aucune interface.
Les assertions personnelles d'un développeur nommé Arek, qu'il pouvait activer sans noyer le reste de l'équipe sous ses diagnostics.
CLCD* dorment dans le binaire : le jeu gérait nativement les claviers Logitech G15 et G510 et leur petit écran monochrome. CLCDStreamingText y faisait défiler objectifs de quête et état du combat ; CLCDGfx dessinait quelques graphismes. Jamais annoncé, presque personne n'avait ce clavier — mais quelqu'un, chez CDPR, en possédait un et trouvait classe d'y voir la barre de vie de Geralt. Alors il l'a construit, intégré, et livré à tout le monde.
L'API Lua compte 2 391 fonctions. La plupart sont raisonnables. Pas toutes.
Fait… roter un modèle 3D. Le nom apparaît dans les gestionnaires d'erreur, preuve que la fonction est bien réelle et appelable.
Retrait de membre. Couplée à la table decap.2da, c'est l'implémentation des coups fatals : le script l'appelle avec la cible et le membre.
Un test booléen dédié : cet objet est-il une huile de lame ? L'alchimie avait besoin d'un chemin rapide pour cette seule question — on mesure à quel point les huiles étaient centrales.
BECOUSE n'était pas seule. Ces chaînes ont voyagé dans chaque copie du jeu, sur toutes les plateformes, dans les quatorze langues — invisibles, car purement internes.
| Chaîne livrée | Aurait dû être |
|---|---|
| SPN_MonsterSpanwScript | Spawn |
| STATE_FADEING | Fading |
| tempoarary module file | temporary |
| aquired (deux fois) | acquired |
| Verion control | Version |
| simultanousely | simultaneously |
| HasBegan | HasBegun |
Aucune ne provoque de bug. Aucune n'est visible. Elles sont, à leur façon, la preuve que des gens ont bâti tout cela — vite, le soir, en anglais qui n'était pas leur langue, dans un bureau de Varsovie en 2007.
Les premiers sont des modules d'aventure indépendants, lancés depuis le menu « Nouvelles aventures ». La campagne principale, elle, s'appuie sur m1_module.adv, m2_module.adv, etc.
« Les marais » — module d'aventure
« Les spectres » — module d'aventure
« Le mariage » — module d'aventure
« Les stratagèmes » — module d'aventure
« Sorceleur-Noël » — module d'aventure
185 Mo — chapitre de la campagne principale
Adam Skorupa et Paweł Błaszczak ont composé la musique du jeu : orchestre, chœurs, instruments folkloriques d'Europe centrale.
Huit pistes, extraites intactes du dossier Soundtracks. Lancez-en une — le fond sonore du dossier s'efface pour lui laisser la place.
Tout ce qui précède partait de la même opération : ouvrir les archives. Voici ce qu'on y trouve en image.
Portraits tirés du journal, scènes peintes, et atlas UV des modèles 3D — les textures « dépliées » qui s'enroulent sur la géométrie d'un personnage.










On lit la géométrie du mesh dans la disposition des éléments.



