Darkwood Blog Blog
  • Articles
fr
  • de
  • en
Connexion
  • Blog
  • Articles

🔀 Darkwaar33 - Seedwake : un mode multijoueur asynchrone sans serveur

le 18 octobre 2026

Connectez-vous pour réagir à cet article

🚀 1

MĂȘme graine. MĂȘme obscuritĂ©. Des choix diffĂ©rents.

Et si le mode multijoueur ne nĂ©cessitait pas que les joueurs soient en ligne en mĂȘme temps ? Et si la comparaison de deux parties ne nĂ©cessitait ni serveur ni base de donnĂ©es, mais simplement une graine et une sĂ©quence d’actions ?

C’est ce que j’ai explorĂ© avec Darkwaar33 - Seedwake, un petit roguelike tactique au tour par tour dĂ©veloppĂ© avec Godot 4.6.

Deux joueurs peuvent explorer exactement les mĂȘmes sept Ă©tages, prendre des dĂ©cisions diffĂ©rentes, puis comparer leurs parties par la suite. Une partie importĂ©e devient un « fantĂŽme », montrant oĂč l’autre joueur est allĂ©, comment il a survĂ©cu et le moment exact oĂč leurs dĂ©cisions ont divergĂ©.

Ce qui est intĂ©ressant, ce n’est pas seulement le gameplay. C’est l’architecture qui rend possible la compĂ©tition asynchrone sans infrastructure en ligne.

Une graine, un monde

La gĂ©nĂ©ration procĂ©durale apporte gĂ©nĂ©ralement de la variĂ©tĂ©. Chaque nouvelle partie produit un monde diffĂ©rent, ce qui favorise l’exploration et la rejouabilitĂ©.

Mais la génération procédurale peut également créer un point de référence commun entre les joueurs.

Dans Seedwake, une graine de 30 bits dĂ©termine l’environnement dans son intĂ©gralitĂ© :

  • Sept Ă©tages, chacun contenant une grille tactique de 7×7.
  • Les positions des murs et l’emplacement des escaliers.
  • Le placement des ennemis et leur comportement initial.
  • Les « glints » Ă  collecter et leurs durĂ©es d’expiration.
  • Les offrandes des sanctuaires entre les Ă©tages.

Deux joueurs partant de la mĂȘme graine se retrouvent face aux mĂȘmes conditions initiales.

Plus important encore, aucun nombre alĂ©atoire n’est gĂ©nĂ©rĂ© pendant le jeu.

Une fois qu’un Ă©tage a Ă©tĂ© gĂ©nĂ©rĂ©, chaque rĂ©sultat obĂ©it Ă  des rĂšgles dĂ©terministes. Les ennemis agissent en fonction de leur Ă©tat actuel, les attaques sont prĂ©visibles, et la mĂȘme action appliquĂ©e au mĂȘme Ă©tat produit toujours le mĂȘme rĂ©sultat.

La graine dĂ©termine le monde. Le joueur dĂ©termine ce qui s’y passe.

Le déterminisme en tant que contrainte architecturale

Godot permet de gĂ©nĂ©rer des nombres alĂ©atoires via RandomNumberGenerator, mais son algorithme sous-jacent est documentĂ© comme un dĂ©tail d’implĂ©mentation.

Pour un jeu classique, cette distinction n’a peut-ĂȘtre pas beaucoup d’importance. Pour un jeu oĂč les joueurs Ă©changent des codes de relecture, elle est en revanche cruciale.

Une rediffusion créée aujourd’hui ne devrait pas produire silencieusement un rĂ©sultat diffĂ©rent aprĂšs une mise Ă  jour du moteur.

Seedwake utilise donc un petit gĂ©nĂ©rateur xorshift32 explicitement dĂ©fini, avec des flux indĂ©pendants dĂ©rivĂ©s Ă  l’aide de FNV-1a.

Les flux sont séparés par responsabilité :

Flux Responsabilité
map Murs, escaliers, reflets et disposition des étages
enemy Positions, orientations et cycles des ennemis
offer Récompenses des sanctuaires

Chaque flux est également isolé par étage.

Modifier la génération du troisiÚme étage ne devrait pas modifier de maniÚre inattendue celle du quatriÚme.

Cette sĂ©paration Ă©vite un problĂšme courant liĂ© Ă  la gĂ©nĂ©ration procĂ©durale : l’utilisation d’un nombre alĂ©atoire supplĂ©mentaire dans un sous-systĂšme peut modifier l’ensemble de la sĂ©quence utilisĂ©e par un autre.

Cela signifie également que les décisions de gameplay ne peuvent pas influencer la génération future par une utilisation accidentelle du générateur de nombres aléatoires (RNG).

Une simulation indépendante de son interface

Seedwake sépare ses rÚgles de la couche de présentation Godot.

L’architecture est dĂ©libĂ©rĂ©ment lĂ©gĂšre :

main.gd
    Entrées, écrans, HUD, animations
 |
 v
rules.gd
    Génération, actions, combat,
    score et simulation
 |
 v
board_view.gd
    Grille, unités, intentions,
    fantĂŽme et retour visuel

codec.gd
    Graines, codes d’exĂ©cution et sommes de contrĂŽle

Le fonctionnement central est conceptuellement simple :

Rules.apply(state, action)

Elle prend un Ă©tat et une action, valide le coup, puis renvoie l’état rĂ©sultant ainsi que les Ă©vĂ©nements dĂ©crivant ce qui s’est passĂ©.

L’interface utilise ces Ă©vĂ©nements pour animer les dĂ©placements, les dĂ©gĂąts, la destruction des ennemis et les transitions.

Les animations ne modifient pas la simulation.

Cette distinction est importante. Une interpolation d’une durĂ©e de 120 millisecondes ne doit pas modifier le rĂ©sultat d’un tour, quels que soient la frĂ©quence d’images ou les performances du navigateur.

La logique du jeu reste indĂ©pendante du rendu, du timing et des pĂ©riphĂ©riques d’entrĂ©e.

Cette mĂȘme architecture rend Ă©galement l’intĂ©gralitĂ© du jeu rejouable.

Une rediffusion n’est qu’une liste de dĂ©cisions

Les enregistrements de jeux traditionnels stockent des images ou des images vidéo.

Seedwake n’a besoin ni de l’un ni de l’autre.

La simulation Ă©tant dĂ©terministe, la reproduction d’une partie ne nĂ©cessite que deux Ă©lĂ©ments :

  1. La graine d’origine.
  2. La séquence ordonnée des actions du joueur.

Une rediffusion est reconstituée en appliquant ces actions à un état initial nouvellement généré.

Rules.simulate(seed, actions)

Le rĂ©sultat comprend l’état final du jeu et une trace de la progression du joueur.

Il n’y a pas de moteur de rediffusion distinct ni de deuxiĂšme implĂ©mentation des rĂšgles de combat.

La mĂȘme simulation est utilisĂ©e pour le gameplay ordinaire, la validation des rediffusions et les fantĂŽmes rivaux.

La rejouabilitĂ© dĂ©coule des rĂšgles elles-mĂȘmes plutĂŽt que d’un sous-systĂšme supplĂ©mentaire ajoutĂ© a posteriori.

Partager une partie sans backend

À la fin d’une partie, Seedwake gĂ©nĂšre un code de partie compact.

Par exemple :

SW1-04V9P-NR2B5-


Ce code contient la version des rĂšgles, la graine, l’identifiant du joueur, les actions enregistrĂ©es, le rĂ©sultat revendiquĂ© et une somme de contrĂŽle.

Les actions utilisent des identifiants Ă  quatre bits, ce qui permet de faire tenir deux actions dans un octet. Les donnĂ©es obtenues sont encodĂ©es Ă  l’aide du codage Crockford base32, un alphabet convivial qui Ă©vite plusieurs caractĂšres ambigus.

Un code peut ĂȘtre copiĂ© dans un message et importĂ© par un autre joueur.

Le jeu qui importe le code ne se fie pas aveuglément au résultat.

Il dĂ©code les donnĂ©es, vĂ©rifie leur intĂ©gritĂ©, valide la version des rĂšgles, puis rĂ©exĂ©cute l’intĂ©gralitĂ© de la sĂ©quence d’actions.

Si le score simulé, les niveaux terminés ou le nombre de tours diffÚrent du résultat déclaré, le code est rejeté.

Il ne s’agit ni d’une authentification cryptographique ni d’un systĂšme anti-triche. Une somme de contrĂŽle ne peut empĂȘcher une falsification dĂ©libĂ©rĂ©e, et les joueurs hors ligne peuvent rĂ©essayer une graine autant de fois qu’ils le souhaitent.

Mais cela fournit un rĂ©sultat reproductible et cohĂ©rent, sans nĂ©cessiter de comptes, de bases de donnĂ©es, d’API ou de simulation cĂŽtĂ© serveur.

Pour un petit jeu partagĂ© entre amis, c’est un compromis utile.

Le « fantĂŽme » est l’historique d’un autre joueur

Une fois qu’un code rival a Ă©tĂ© validĂ©, sa trace de simulation devient un « fantĂŽme ».

Le fantĂŽme apparaĂźt sous la forme d’un losange cyan creux se dĂ©plaçant sur les mĂȘmes Ă©tages que le joueur.

Il ne participe pas aux combats, ne bloque pas les dĂ©placements et n’influence pas les dĂ©cisions des ennemis.

En revanche, il fournit des informations.

Au troisiĂšme Ă©tage, au cinquiĂšme tour, le jeu peut afficher l’endroit oĂč se trouvait l’adversaire au troisiĂšme Ă©tage, au cinquiĂšme tour.

Si l’adversaire a dĂ©jĂ  atteint l’escalier, le jeu indique Ă  quel moment il en est sorti. S’il est mort, sa position finale devient un marqueur de mort.

Une courte traßnée de positions précédentes rend les déplacements du fantÎme lisibles.

C’est le multijoueur asynchrone rĂ©duit Ă  son essence informationnelle.

Les deux joueurs n’échangent jamais de messages avec un serveur. Leurs parties n’ont jamais besoin de s’exĂ©cuter simultanĂ©ment. Ils partagent simplement un monde dĂ©terministe et comparent leurs historiques.

DĂ©terminer le moment exact oĂč deux parties divergent

La fonctionnalité que je trouve la plus intéressante est le marqueur de divergence.

Imaginez deux joueurs partant d’une mĂȘme graine.

Ils effectuent le mĂȘme premier coup, puis le mĂȘme deuxiĂšme coup, puis le mĂȘme troisiĂšme coup.

La simulation étant déterministe, leurs états restent identiques.

Au quatriĂšme tour, l’un se dĂ©place vers le nord tandis que l’autre repousse un ennemi vers l’est.

C’est là la premiùre divergence.

Seedwake enregistre cette dĂ©cision et marque sa position sur le plateau Ă  l’aide d’un symbole en forme de fourche.

À partir de ce moment, les deux parties peuvent Ă©voluer vers des rĂ©sultats complĂštement diffĂ©rents.

Un joueur pourrait rĂ©cupĂ©rer une Ă©tincelle sur le point de s’éteindre. L’autre pourrait Ă©liminer un ennemi, prĂ©server sa santĂ© ou atteindre l’escalier plus tĂŽt.

Le jeu peut identifier l’action prĂ©cise Ă  partir de laquelle l’histoire commune s’est scindĂ©e en deux chemins diffĂ©rents.

C’est plus intĂ©ressant que de se contenter de comparer les scores finaux.

Un score vous indique qui a obtenu les meilleurs résultats selon le systÚme de notation.

Un marqueur de divergence vous indique à quel moment leurs décisions ont commencé à diverger.

La comparaison devient alors un moyen de comprendre le jeu lui-mĂȘme.

Rendre les rĂšgles tactiques lisibles

L’architecture dĂ©terministe n’aurait guĂšre de sens si les joueurs ne pouvaient pas comprendre les consĂ©quences de leurs actions.

Seedwake utilise donc un plateau tactique compact et entiĂšrement visible.

Il existe trois types d’ennemis :

  • Les Brutes poursuivent le joueur et attaquent les cases adjacentes.
  • Les Lanciers tirent dans une direction fixe selon un cycle de trois tours.
  • Les Sentinelles attaquent pĂ©riodiquement leurs quatre cases voisines.

Leurs intentions s’affichent avant que le joueur n’agisse.

Les cases hachurĂ©es en rouge indiquent oĂč les attaques vont frapper.

Le joueur peut se déplacer, attendre, sprinter ou pousser des ennemis. Pousser un ennemi contre un mur, un autre ennemi ou le bord du plateau le détruit.

Les attaques ennemies peuvent Ă©galement toucher d’autres ennemis.

Cela crĂ©e de petits dĂ©fis tactiques oĂč le positionnement importe davantage que la vitesse de rĂ©action.

Les reflets ajoutent une deuxiĂšme dimension dĂ©cisionnelle. Ils disparaissent aprĂšs un nombre limitĂ© de tours et peuvent soit ĂȘtre conservĂ©s pour marquer des points, soit dĂ©pensĂ©s dans des sanctuaires pour obtenir des amĂ©liorations de survie.

Le joueur doit constamment choisir entre collecter des ressources, Ă©viter le danger et progresser vers l’escalier.

Comme les rÚgles ne comportent aucun élément aléatoire en cours de partie, les conséquences de ces décisions sont reproductibles.

Pourquoi pas de serveur ?

Un classement en ligne serait possible.

Un backend pourrait collecter les scores, gérer les défis quotidiens, valider les soumissions et diffuser les parties des adversaires.

Mais cela impliquerait Ă©galement des contraintes liĂ©es Ă  l’infrastructure, Ă  l’authentification, au stockage, Ă  la prĂ©vention des abus, Ă  la maintenance et au dĂ©ploiement.

Pour Darkwaar33, je voulais tester jusqu’oĂč le concept de multijoueur asynchrone pouvait aller sans tout cela.

Le résultat prend en charge trois interactions simples :

  • Partager une graine pour relever le mĂȘme dĂ©fi.
  • Partager un code de partie pour comparer les performances complĂštes.
  • Importer un code de partie pour jouer aux cĂŽtĂ©s du fantĂŽme d’un autre joueur.

Le navigateur exécute tout localement.

La limite est tout aussi claire : il n’y a ni recherche automatique d’adversaire, ni classement global, ni application contraignante des tentatives quotidiennes.

Ces fonctionnalitĂ©s nĂ©cessiteraient un modĂšle d’infrastructure diffĂ©rent.

Pour cette expérience, elles ne sont pas nécessaires.

Un petit jeu, une idée réutilisable

Darkwaar est une expérience en cours visant à créer et à publier réguliÚrement de petites idées jouables.

Seedwake suit cette approche : un plateau limitĂ©, un petit ensemble d’actions, sept niveaux, trois types d’ennemis et une mĂ©canique asynchrone distinctive.

Le projet utilise Godot 4.6 et GDScript, avec une exportation HTML5 publiée via le pipeline existant de Darkwaar.

La leçon plus générale dépasse le cadre des roguelikes.

Lorsqu’une application est construite autour de transitions d’états dĂ©terministes, une sĂ©quence d’entrĂ©es peut devenir une reprĂ©sentation compacte et portable d’une exĂ©cution complĂšte.

Cela ouvre des possibilités pour les systÚmes de relecture, le débogage, les tests reproductibles, les simulations et la collaboration asynchrone.

Dans Seedwake, cela se traduit par un fantÎme qui marche à vos cÎtés dans un donjon.

Mais en rĂ©alitĂ©, il s’agit simplement d’une nouvelle exĂ©cution des mĂȘmes rĂšgles.

MĂȘme graine. MĂȘme obscuritĂ©. Des choix diffĂ©rents.

Jouez Ă  Darkwaar33 - Seedwake

Jouez Ă  Seedwake sur itch.io

Sept étages. Une graine partagée. Aucun serveur requis.

Jouez de maniĂšre indĂ©pendante, partagez votre code d’exĂ©cution et mettez quelqu’un d’autre au dĂ©fi de prendre de meilleures dĂ©cisions dans exactement le mĂȘme monde.

Connectez-vous pour réagir à cet article

🚀 1

Site

  • Plan du Site
  • Contact
  • Mentions lĂ©gales

Network

  • Hello
  • Blog
  • Apps
  • Photos

Social

Darkwood 2026, tous droits réservés