BeamNG.Earth
BeamNG.drive용 1:1 비율 실시간 지구, 실제 고도·위성 이미지·부동 원점·보조 서버에서 LOD 스트리밍
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Le Skyjo à deux (ou à huit), chacun sur son téléphone. Une page web, un code à trois chiffres, et c'est parti. Pas de compte, pas de pub, pas de store. Règles officielles, y compris les deux qui font le sel du jeu : l'élimination des colonnes de trois cartes identiques, et le doublement du score de celui qui ferme la manche sans avoir, à lui seul, le plus petit total.README 전문 보기 ↗
Le Skyjo à deux (ou à huit), chacun sur son téléphone. Une page web, un code à trois chiffres, et c'est parti. Pas de compte, pas de pub, pas de store.
Règles officielles, y compris les deux qui font le sel du jeu : l'élimination des colonnes de trois cartes identiques, et le doublement du score de celui qui ferme la manche sans avoir, à lui seul, le plus petit total.
Deux règles maison s'y ajoutent, assumées. Une ligne entière de cartes identiques saute elle aussi — les quatre sur une grille intacte, les trois qui restent quand une colonne éliminée a fait un trou dedans : un trou n'est pas une carte qui dépareille. Et la carte en main est publique, même sortie de la pioche : à distance, une carte grise au milieu de la table ne raconte rien de ce que l'adversaire est en train de peser. Le jeu de société ne connaît ni l'une ni l'autre ; la page Règles de l'application signale les deux écarts pour qu'on ne les emporte pas par erreur sur une vraie table.
Et un mode spicy, en option, que l'hôte choisit dans le salon avant de
distribuer. Il n'ajoute rien au tour de jeu — ni au comptage, ni aux
éliminations, ni à la pénalité de fermeture : il ajoute trois cartes au paquet.
Deux -5, du point sec (à deux exemplaires, une colonne de -5 reste
impossible). Un joker, qui vaut 0 et complète n'importe quel groupe : une
colonne 7 / joker / 7 saute — et quand il ferme un groupe, il ne part pas à la
défausse avec le reste : il quitte la manche. Sur la défausse, le joueur
suivant n'avait qu'à se servir ; rendu à la pioche, il revenait à quelqu'un qui
n'avait rien fait pour l'avoir, et le voir y retourner annonçait à toute la
table qu'il reviendrait. Un joker se gagne une fois et se dépense une fois : sa
case s'efface sur place, sans voler vers aucune pile. Et quatre cartes Vol,
qui échangent une de tes cartes contre celle d'un adversaire, face visible ou
non : la carte garde sa face en changeant de grille, donc un dos volé reste un
dos — personne ne l'a vu, pas même le voleur — et le nombre de dos change de
camp, jusqu'à fermer la manche quand on donne son dernier. Les deux grilles
rejouent ensuite leurs éliminations, si bien qu'on peut prendre la carte qui
ferme sa colonne comme laisser à sa victime celle qui ferme la sienne.
Le Vol n'a pas de valeur, donc pas de place dans une grille : il n'existe que
dans la pioche, et ne se déclenche qu'en étant pioché. Ce n'est pas un détail
d'implémentation mais la règle qui fixe sa fréquence — environ un par manche à
deux joueurs. Le joker, lui, vit dans une grille, et c'est ce qui a coûté le
plus cher : une case de grille cesse d'être un nombre. Elle reste pourtant
value: 0 avec un drapeau, pour que le comptage n'ait pas une ligne à changer ;
seules les éliminations regardent le drapeau, parce que ce sont elles que le
joker change.
Comme sur les flippers : trois lettres et un score, sans compte ni modération. Au Skyjo le meilleur score est le plus bas, alors la borne a deux écrans — les meilleurs scores (les totaux les plus bas parmi ceux qui n'ont pas franchi la barre) et le mur de la honte (les plus hauts parmi ceux qui l'ont franchie). La barre partage proprement les deux : chacun n'a droit qu'à un tableau, et chaque partie en nourrit au moins un.
Il se montre à deux moments. En fin de partie, sur chaque téléphone : si ton total entre dans les dix, la borne te demande tes initiales aux molettes ▲ ▼ (on peut tenir le bouton), puis fait clignoter ta ligne. Sur l'accueil, laissé vingt secondes sans qu'on y touche : il passe en mode démo, comme une borne au fond d'un bar, et fait défiler titre, records et honte jusqu'à ce qu'on le touche. Le lien « Les records » y mène tout de suite.
La seule vérification qui vaille est côté serveur : le score n'est jamais lu
dans la requête, mais dans l'état de la partie terminée. Le reste, on s'en
fiche. Les records vivent dans skyjo.records, à part des parties, et
survivent à leur purge.
navigateur ──POST /api/games/:code──▶ Next.js ──RPC──▶ Postgres
▲ │
│ moteur de règles (TS pur)
│ │
└────── Supabase Realtime ◀───────────┘
(la vue d'après, en entier)
Trois idées structurent le tout.
Le moteur ne connaît ni le réseau ni la base. src/lib/skyjo/ est un
réducteur pur : (état, action) → état. Il se teste sans rien démarrer, avec un
mélange déterministe par graine, et c'est là que vivent toutes les règles.
Le serveur fait autorité. Le navigateur n'écrit jamais dans la base et ne lit
jamais l'état complet d'une partie. Il envoie une intention, le serveur la
valide contre le moteur, écrit le nouvel état, puis renvoie une projection
expurgée (toView) : les cartes face cachée y sont présentes mais sans leur
valeur, et la pioche se résume à un compteur. La carte en main, elle, est
publique — c'est une règle maison, pas un oubli : ce qui reste secret, c'est
l'ordre de la pioche et le dos des grilles. Tricher demanderait de deviner
l'état, pas de lire une réponse.
Le temps réel ne transporte rien de secret. Deux tables : skyjo.games
(méta publique — code, version, à qui de jouer) diffusée en temps réel, et
skyjo.game_states (l'état complet) sur laquelle la RLS est active sans aucune
policy, donc illisible pour tout rôle client. Ce qui circule sur le canal, c'est
la projection expurgée — la même que le serveur rendrait à qui la demanderait, et
rien de plus.
Les écritures utilisent un verrou optimiste : skyjo_commit_state n'écrit que si
la version en base n'a pas bougé. Si les deux joueurs tapent en même temps, le
perdant rejoue son action sur l'état frais — que le refus lui rapporte, plutôt
que de le lui faire redemander — et se fait proprement refuser par le moteur si
ce n'était plus son tour. C'est aussi ce qui permet au serveur de garder les
parties en mémoire entre deux requêtes sans rien parier : jouer sur un état
périmé ne produit pas un état faux, ça produit un refus qui porte de quoi
recommencer.
Et c'est la vue qui se diffuse, pas une notification. toView était déjà
identique pour tous les joueurs — seuls you et legalActions nomment leur
lecteur, et les deux se recalculent depuis la vue elle-même (viewFor). Le
serveur publie donc une seule projection sur le canal de la partie, et chaque
téléphone se l'adresse. Apprendre le coup d'en face ne coûte plus un
aller-retour complet de plus que le coup lui-même — ce qui, avant, retombait
pile pendant son animation. La ligne écrite dans skyjo.games reste le filet :
une diffusion ne se rejoue pas, une ligne en base finit toujours par se voir.
Le détail de ce que ça coûtait, mesures à l'appui, est dans
docs/PERFORMANCE.md.
npm install
npm run dev
Sans configuration, les parties sont gardées en mémoire dans le processus : suffisant pour développer et pour ouvrir deux onglets. Rien ne survit à un redémarrage, et ce repli est refusé en production.
Pour brancher la vraie base, crée un .env.local à partir de .env.example.
Le schéma SQL est dans supabase/migrations/. Applique-le sur ton projet
Supabase, puis renseigne trois variables d'environnement côté hébergeur :
| Variable | Où la trouver | Exposée au navigateur |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase → Project Settings → Data API | oui |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Supabase → Project Settings → API Keys (clé publishable) | oui |
SUPABASE_SERVICE_ROLE_KEY | Supabase → Project Settings → API Keys (clé service_role / secret) | non, jamais |
La clé de service donne un accès complet à la base : elle ne doit exister que dans les variables du serveur.
La clé publique compte aussi, et son échec est muet. C'est elle qui ouvre le canal temps réel : mauvaise clé, le WebSocket est refusé en 401, la partie continue de marcher — par le sondage de secours, douze fois plus lent et douze fois plus gourmand — et rien à l'écran ne le dit. Le client le signale dans la console du navigateur ; en cas de doute, c'est la première chose à regarder.
La région compte. Un coup fait un ou deux appels à la base, l'un après
l'autre : posés de l'autre côté d'un océan, ils coûtent à eux seuls plus que
tout le reste du tour, ils traînent une queue de latence qui finit en
504 Gateway Timeout, et ça ne se voit pas en développement. La région se règle
dans vercel.json (regions), pas dans les routes : preferredRegion est
déprécié par Next, et un plan Hobby n'applique de toute façon qu'une région à
tout le projet. À aligner sur celle du projet Supabase — cdg1 (Paris) pour
eu-west-3. Ça se vérifie sur n'importe quelle réponse : l'en-tête
x-vercel-id commence par la région qui a servi.
Le schéma vit dans un espace skyjo dédié, séparé de public, et n'est pas
exposé à PostgREST : le serveur y accède par quatre fonctions SQL réservées au
rôle de service. Le nettoyage des parties abandonnées se fait avec
select public.skyjo_purge_stale_games();.
npm test # règles, comptage, confidentialité, chronologie des animations
npm run typecheck
npm run smoke # partie complète jouée par HTTP (serveur de dev requis)
Le fichier src/lib/skyjo/engine.test.ts sert aussi de spécification lisible :
chaque règle y a son test nommé en français, les officielles comme la maison —
dont un test qui vérifie que trois cartes identiques dans une ligne intacte ne
suffisent pas, et un autre que les mêmes trois cartes suffisent dès qu'une
colonne éliminée a raccourci la ligne. Le mode spicy y a ses propres sections :
qu'un Vol ne se trouve jamais ailleurs que dans la pioche, qu'un dos échangé
reste caché et ne s'annonce à personne, que renoncer coûte un retournement,
qu'un joker complète une colonne et une ligne du même coup, qu'il quitte alors
la manche sans se poser sur aucune pile — une fois, même s'il a fermé les deux
groupes — et qu'il perd son pouvoir dès qu'on pose une carte dessus.
Tout ce fichier-là se joue à deux, parce que c'est le cas le plus fréquent et
celui sur lequel chaque règle se lit le mieux.
src/lib/skyjo/multiplayer.test.ts prend l'autre bout : des parties entières à
trois, quatre, cinq et huit joueurs, jouées au hasard parmi les coups légaux
sur des graines fixes. Ce qu'il attrape n'est pas une règle mais un blocage —
une étape de tour dont aucun coup ne sort, une rotation qui saute quelqu'un, un
dernier tour qui ne dure pas exactement un coup par adversaire. Ce que deux
joueurs ne pouvaient pas tester, en somme : à deux, une rotation est un
aller-retour et « le plus petit total » n'a qu'un concurrent. Le reste de ce que
le passage à quatre change — écritures simultanées, lisibilité, dosage du mode
spicy — est mesuré dans docs/AUDIT-MULTIJOUEUR.md.
src/lib/server/contention.test.ts tient le seul endroit du jeu où plusieurs
joueurs écrivent en même temps : le retournement initial, qui revient à chaque
manche. Il se joue contre un dos à latence, parce que sans elle les requêtes se
sérialisent d'elles-mêmes et la contention n'existe pas — ce qui est exactement
pourquoi le défaut ne se voyait pas en développement.
npm run smoke joue deux parties complètes par HTTP, une par mode. Les règles
du vol et du joker sont couvertes au coup par coup côté moteur, avec une graine
fixe ; ce que le smoke vérifie en plus, c'est que les trois actions ajoutées
franchissent la validation et le magasin — un Vol tiré au hasard ne peut pas
s'en charger, il ne sort pas à toutes les parties.
src/lib/client/flights.test.ts teste l'autre moitié : la chronologie d'un
coup. Une carte prise, une carte posée, une carte jetée et une colonne qui
saute, jouées en même temps, font un clignotement ; c'est leur ordre qui les
rend lisibles, et deux calques indépendants doivent s'accorder à la
milliseconde pour qu'une carte éliminée ne s'affiche pas deux fois.
src/lib/skyjo/ moteur pur : types, règles, réducteur, projection client
src/lib/server/ magasin (Supabase ou mémoire), validation, réponses HTTP
src/lib/client/ identité locale, temps réel, retours sonores et haptiques
src/lib/channel.ts le nom du canal de diffusion, partagé par les deux côtés
src/components/ cartes, grilles, table, écrans de score
src/app/ accueil, salon + partie, règles, API
supabase/ migrations SQL
Skyjo est un jeu de Magilano. Ce dépôt est une implémentation personnelle de ses règles, sans lien avec l'éditeur.