Le modèle : carte, niveau, bundle, projet, bibliothèque, release¶
Note
Technical reference — French only for now. / Référence technique, en français pour l'instant.
Ce document répond, côté développeur, à la question qui revient toutes les semaines : qu'est-ce qui compile l'art d'un jeu, où vit quoi, et qu'est-ce qui part sur la carte ? La description pour l'auteur de jeux est ailleurs — Projects, maps, levels, releases et Working in the App — et n'est pas redite ici : cette page dit les tables, les invariants, et pourquoi ils sont là.
La source de vérité est le service Go de papyDeck_App : le schéma dans
internal/store/migrations/*.sql (à lire dans l'ordre, les dernières
réécrivent les premières), les types dans internal/store/, les routes dans
internal/web/server.go, les règles dans internal/web/. Si le code et ce
document divergent, le code a raison — et il faut le dire. État : mi-octobre
2026, migration 047.
Le vocabulaire, en une table¶
| Mot | Ce que c'est | Où il vit |
|---|---|---|
| Compte | une personne : ses identités OAuth (Google, GitHub), ses adresses vérifiées, son pseudo unique | account, identity, account_email, session |
| Projet | le jeu en cours d'écriture : un dossier plat de .lua (main.lua, toujours), de .mask et de sons |
project, project_file |
| Bibliothèque | les sprites du compte, en vraies couleurs (RGB/RGBA) : un sprite seul (sprite) ou une ligne d'animation (anime, le nombre de frames dans le nom : torche_4) |
asset, asset_frame |
| Modèle | ce qu'un niveau place : une boîte, une ancre, un état par défaut et des états, chacun sur un sprite avec sa cadence, sa boucle et son effet. Partagé par les niveaux du compte | modele (etats en JSON) |
| Carte | le dessin : tuiles 8×8 (et leur tag solide), tampons, terrains, calques, décors posés, calque de collision, sources | carte, carte_calque, carte_tampon, carte_decor |
| Niveau | une carte et ce qui s'y joue : les objets (modèles placés), les réglages de compilation, l'aperçu — l'unité de compilation. Plusieurs niveaux peuvent partager une carte | niveau |
| Bundle | le produit de la compilation d'un niveau, sous le nom du niveau : palette, atlas, images indexées, fond tuilé, variantes, et la recette qui les a donnés | bundle, bundle_asset, bundle_image, bundle_fond, bundle_variante |
| Attache | « ce projet embarque le niveau N, vu par le jeu sous le nom V » — une référence, pas une copie | project_bundle |
| Groupe (côté console) | ce que la console voit d'un niveau : V.pal, V.atlas, ses PNG indexés, ses variantes V.<v>.pal, son fond V-fond.map + V-fond-tiles.png |
le dossier du jeu |
| Release | l'instantané publié : un dossier complet — les fichiers du projet plus les pièces de ses attaches — figé, identifié par l'empreinte de son manifeste | release, release_file, release_niveau |
| Publics | les sprites et les niveaux publiés, en CC0 ou en CC BY, instantanés que les autres comptes copient | asset_public, asset_public_frame, niveau_public, niveau_public_asset, et leurs journaux *_retrait |
| Audio | les sons du compte (effets .sfx, morceaux .son), groupés en bundles audio qu'un projet attache |
audio, audio_bundle, audio_bundle_piece, project_audio_bundle |
Donc : un niveau = un bundle = un groupe sur la console. Le bundle n'est
qu'une mécanique de stockage et de publication ; il porte le nom du niveau, et
s'attache sous le nom que le jeu voit (V, le nom du niveau, sauf pour une copie
renommée — voir plus bas). Le jeu écrit gfx.background("V-fond"),
phy.anim_load("V/torche_4"), ou ne l'écrit pas : le V.lua généré le fait pour lui.
Le nom d'une carte n'arrive jamais à la console.
Le firmware dit « le dossier du jeu » pour l'ensemble que gfx.bundle() compile
d'un coup, tous les groupes : c'est la release ici, pas un bundle de l'App.
Seule la compilation d'un niveau produit un bundle : un sprite n'entre dans un jeu que si un modèle qui le porte est placé dans un niveau, même invisible. Jusqu'à la migration 047 (octobre 2026), la carte portait aussi les objets et se compilait elle-même ; la 047 a séparé le dessin (la carte) de ce qui s'y joue (le niveau), sans compatibilité : cartes, bundles à fond, cartes publiques et releases qui en dépendaient ont été effacés, modèles et sprites gardés.
Un niveau désigne sa carte par la clé (account_id, carte_id) →
carte (account_id, id) : une carte d'un autre compte est impossible par
construction. La clé est en NO ACTION, si bien qu'une carte qu'emploie un niveau
ne se supprime pas, alors que la suppression d'un compte emporte les deux d'un bloc.
Les octets : par empreinte, jamais par chemin¶
- Le magasin d'objets range sous
blob/<sha256>. Postgres ne garde que les métadonnées et l'empreinte. Deux fichiers identiques — le cas normal dès le premier clone — partagent un objet : publier, cloner, copier un public ne copient aucun octet, seulement des lignes. - Le prix : rien ne s'efface avec sa ligne. Un balayage prend le complément de
ReferencedBlobs(internal/store/blobs.go). Toute colonne qui range une empreinte doit y entrer ; le testTestToutesLesTablesAHashSontBalayeesdécouvre ces colonnes par le schéma et échoue sinon. Oublier une table, c'est effacer des fichiers vivants (c'est arrivé, début septembre 2026). - Les cartes et les niveaux, eux, vivent en base (
bytea, JSON). Une carte se réécrit à chaque enregistrement et reste bornée (quelques centaines de Kio au pire) : la déduplication par empreinte n'apporte rien à un document qui change tout le temps. - Concurrence par
If-Match. Un fichier de projet s'écrit sous l'ETag de sa lecture (l'empreinte), une carte ou un niveau sous le sien : deux onglets ne s'écrasent pas, le second reçoit 412. - Le serveur ne calcule pas l'art. Découpe, quantification, tuilage, atlas
tournent en wasm dans le navigateur (
/sim/pdgfx.mjs, lecore/du simulateur, piloté parstatic/compiler.js). Un second calcul en Go divergerait du premier ; le serveur range des octets et vérifie ce qui se vérifie sans calculer (PNG lisibles, tailles, noms, bornes). Le navigateur ne relit jamais les pixels d'un canvas pour en faire des données : Brave les bruite. Le monde aplati, l'aperçu et les planches importées se composent ou se décodent en JS (png.js) ; les canvas ne servent qu'à l'affichage.
Ce qui va où, et pourquoi¶
- Le projet ne contient que du texte et des sons. On y crée des
.lua,.mask,.son(creerFichier: jamais vide, jamais par-dessus un fichier existant) ; on y dépose en plus des.sfx, et des.wavconvertis en.sfxà l'envoi. Une image déposée est refusée : elle échapperait au calcul de la palette sur l'ensemble. Une pièce de niveau n'est jamais un fichier du projet (046) : un.png,.pal,.atlasou.mapvient d'un niveau attaché ; ce que le dossier Levels ne montre pas, le jeu ne l'a pas. main.luanaît avec le projet et ne meurt qu'avec lui.naitreProjetpose un squelette (_update,_draw) ;supprimerFichierrefuse de le supprimer — c'est le point d'entrée.PUTne fait que modifier un fichier existant : créer et supprimer ont leurs routes.<niveau>.luaest généré, et marqué. Sa première ligne porte-- papyDeck:niveau <V> v1. La compilation n'écrase qu'un fichier qui porte ce marqueur ; un<V>.luaécrit à la main est gardé, la compilation le dit (réponse 409) et écrit le reste. Le texte est produit en Go, déterministe : une recompilation sans changement n'écrit rien. Les.luade l'ancien marqueur (-- papyDeck:carte) ont été retirés des projets, avec leur.mask, par une purge unique au démarrage qui suit la migration 047 (047_purge_lua).<niveau>.maskest régénéré à chaque compilation, sans marqueur ni garde : c'est le calque de collision de la carte OU les masques des décors posés, une case par tuile. Un.maskdu même nom écrit à la main est remplacé.- Un effet est une propriété du bitmap. Additif, translucide, contour, derrière le fond : ce sont des rangées de palette. Ils se règlent sur l'état du modèle, valent pour tous les niveaux, et un même sprite ne peut pas porter deux effets dans un niveau (la compilation refuse).
- Publier matérialise, le projet référence. La release embarque les pièces des attaches (graphiques et audio) ; le projet ne garde que l'attache. Recompiler un niveau change la prochaine publication, pas la précédente.
- Rien ne se supprime sous un jeu. La base le garantit, ou une requête le vérifie avant d'écrire :
| On ne supprime pas… | tant que… | Garde |
|---|---|---|
| un sprite | un niveau compilé l'emploie | clé bundle_asset.asset_id, différée → ErrAssetEmploye |
| un niveau (et son bundle) | il est attaché à un projet | clé project_bundle.bundle_id, différée → ErrBundleAttache |
| une carte | des niveaux la posent | clé niveau (account_id, carte_id), NO ACTION → ErrCarteEmployee (409, qui les nomme) |
| un modèle | un niveau le place | requête sur niveau.objets (JSON) → ErrModelePlace |
| un son | un bundle audio l'emploie | clé audio_bundle_piece.audio_id, différée → ErrAudioEmploye |
| un bundle audio | un projet l'attache | clé project_audio_bundle.bundle_id → ErrAudioBundleAttache |
| un sprite public | un niveau public l'emploie | requête sur niveau_public_asset |
| un niveau public | un jeu publié l'emploie | clé release_niveau.niveau_public_id, sans cascade |
main.lua |
toujours | supprimerFichier |
Les clés sont différées (deferrable initially deferred) et non restrict,
et la nuance compte : restrict interdirait aussi de supprimer un compte, dont
la cascade détruit sprites et bundles dans un ordre que Postgres ne garantit pas.
Différé, le contrôle a lieu au commit, quand les lignes qui protégeaient sont
parties avec leur propriétaire : « on ne supprime pas un sprite employé, mais un
compte s'en va d'un bloc ».
Compiler un niveau¶
POST /api/niveaux/{nom}/compiler, en multipart. L'atelier enregistre la carte
si elle a des modifications, puis le niveau, et construit dans le navigateur :
- le monde aplati de la carte : tous les calques, masqués compris, du bas vers le haut, chacun suivi de ses décors posés — l'œil et le verrou ne concernent que l'éditeur ;
- la recette : les sprites distincts des modèles placés, chacun avec l'effet de ses états (63 sprites plus le fond au plus par construction) ;
- le plan des rangées (opaques, translucides, additifs, ombres, derrière) et la rampe du ciel (64 index au plus, en queue) : 255 rangées au plus, la rangée 0 étant transparente ;
- le wasm quantifie le fond et les sprites ensemble — une palette — puis retuile le fond (tuiles distinctes par fréquence, miroirs fusionnés, 704 au plus, regroupement par similarité au-delà) ; les variantes dérivent leurs couleurs de cette palette ;
- un aperçu PNG du niveau en taille réelle, que sa publication et celle d'un jeu reprendront.
Le formulaire porte aussi carte_maj, l'ETag de la carte sur laquelle le fond a
été construit : si la carte a changé depuis, le serveur répond 409 — le fond et le
.mask (tiré de la carte en base) ne viendraient pas du même dessin. Le serveur
(niveau_compiler.go) range les pièces par empreinte, exige un fond et un seul,
nommé fond, refuse ce que la console refuserait — un objet hors de la carte, un
modèle placé qui n'existe plus, deux modèles placés que le jeu verrait sous la
même clé, deux effets pour un sprite, plus de 64 états à animer (la console en
tient 64 à la fois ; M.decharger() du <niveau>.lua les rend avant le niveau
suivant), une recette qui ne couvre pas les sprites placés, un nom complet de
plus de 47 caractères —, puis écrit :
| Pièce | Table | Nom dans le dossier du jeu |
|---|---|---|
| palette, 768 o (512 de couleurs RGB565, 256 de modes) | bundle.pal_hash |
V.pal |
| atlas (régions, format Kivy multi-pages) | bundle.atlas_hash |
V.atlas |
| une image indexée par sprite ou animation | bundle_image |
son nom, préfixé du niveau |
| le fond : carte texte et planche des tuiles | bundle_fond (nom court fond) |
V-fond.map, V-fond-tiles.png |
les variantes (réglages et .pal dérivé, mêmes modes) |
bundle_variante |
V.<v>.pal |
| la recette : sprites, effets, dans l'ordre | bundle_asset |
— |
| le plan et le ciel | bundle.plan_*, ciel_* |
— |
| l'aperçu | niveau.apercu_hash |
— |
PutBundle est un remplacement complet sous le nom du niveau : un bundle ne se
renomme pas, et un nom de niveau doit être libre comme niveau et comme bundle.
Le bundle et les .mask s'écrivent même quand un .lua est refusé. Puis, pour
chaque projet coché : le contrôle des bornes, l'attache sous V (un upsert :
recompiler repointe), V.mask, V.lua. La réponse dit, projet par projet, ce
qui a été écrit ou refusé. Le niveau note enfin carte_compilee, la date de la
carte compilée : un niveau est périmé quand la carte est plus récente, et
l'atelier le marque recompile.
Les réglages de compilation — plan, ciel, variantes, projets cochés — sont
enregistrés avec le niveau (niveau.compil, JSON). Détacher
(DELETE /api/projects/{slug}/bundles/{V}) retire le lien, et avec lui ce que
la compilation avait écrit : V.lua s'il porte le marqueur (un fichier écrit à
la main reste) et V.mask. Un .lua généré qui resterait ferait croire à la
publication que le jeu emploie encore le niveau. Supprimer un niveau supprime son bundle,
et c'est refusé tant qu'il est attaché.
Les attaches, et les bornes de la console par projet¶
project_bundle = (projet, bundle, nom vu), clé (projet, nom vu). Les pièces ne
sont pas copiées dans le projet : PiecesDeProjet les calcule à la lecture,
aux empreintes du bundle. GET /api/projects/{slug} les liste à part des fichiers
(elles ne s'éditent pas), GET /api/projects/{slug}/files/{nom} les sert (le
fichier du projet d'abord, la pièce ensuite) : c'est ce que le simulateur et la
console demandent, par nom plat, comme require().
La console tient, par jeu, 12 palettes, 16 fonds et 2 Mio de pièces
graphiques (PD_LIB_BYTES). Rien ne les comptait avant mi-septembre 2026, et un
jeu les découvrait au lancement. Comptés sur les attaches (bornesDuProjet) :
- palettes : 1 par niveau attaché, plus ses variantes ;
- fonds : les lignes
bundle_fond(un niveau en apporte un ; deux niveaux sur une même carte en apportent deux) ; - octets :
.pal,.atlas, variantes, images,.mapet planches, tailles lues dans le magasin.
La compilation compte le projet sans l'attache de même nom vu, plus le niveau avec ses nouvelles pièces, et refuse d'attacher si une borne déborde. C'est le seul moment où le compte se vérifie ; la fiche du projet le montre. Le quota du projet est une autre affaire : il ne compte que ses fichiers.
Les releases¶
POST /api/projects/{slug}/publish, corps {notes, licence, confirme, categorie}.
- Une release est un dossier complet.
release_filereçoit les fichiers du brouillon, puis les pièces des attaches graphiques et audio, nommées comme le jeu les voit ; un fichier du projet l'emporte sur une pièce homonyme. L'export.zipdu brouillon (/projets/{slug}/export.zip) fait le même dossier. - Le manifeste.
manifest_hash= sha256 des fichiers triés par nom, chacun rendunom \0 empreinte \n. Non unique : deux releases identiques au bit près le partagent, et c'est ainsi qu'on repère un clone jamais modifié. Le même calcul sur le brouillon, pièces comprises, répond à « le projet a-t-il changé depuis la dernière version ? ». - La version est
max + 1par (compte, slug), sous verrou du projet. Le pseudo de l'auteur n'est pas stocké, il est joint à la lecture : un pseudo change,account_ididentifie.project_idpasse à null si le projet est supprimé : une version publiée lui survit. - La filiation. Cloner pose
project.parent_release_id; chaque release du clone le recopie dansrelease.parent_release_id. C'est une filiation, pas une succession (la version la dit déjà), remontée sur 32 générations au plus. - Un jeu se publie avec ses sources. Ses niveaux sont ses attaches et
chaque
.luaqui porte le marqueur de l'atelier (un clone n'a pas d'attache avant que l'atelier compile ses niveaux) — le nom vu se résout en niveau parnom_jeu. Chacun doit exister, avoir un aperçu, ne pas être périmé, et ses modèles et sprites aussi ; rien de ce qu'il contient ne doit être non déclaré ni signalé. Il faut alors la case et une catégorie de niveau. La licence est celle que les éléments permettent (licence.Combiner) : CC BY dès qu'un élément l'est, uncredits.txtgénéré entre alors dans la release. Dans une transaction : chaque niveau est publié en niveau public (republié seulement s'il a changé), ses sprites privés entrent dans la bibliothèque publique (catégoriedivers, tag tiré du slug), la release est figée, etrelease_niveaunote (release, niveau public, version, nom vu). Tout ou rien. La migration 047 a supprimé les releases qui avaient unerelease_carte, avec une ligne de journal. - Retirer un jeu supprime ses releases (
RetirerJeu), toutes versions, avec une ligne de journal (release_retrait). Fichiers etrelease_niveaupartent en cascade, lesparent_release_idqui les désignaient passent à null ; les projets clonés restent, les niveaux publics aussi, et redeviennent retirables. - La galerie (
GET /api/gallery) rend la dernière version de chaque jeu. Les tutoriels — un jeu d'administrateur dont le slug commence partuto_— n'y sont que sur demande (?tutos=1) : les consoles en service lisent cette liste.
Publics, copies et clones¶
Les sprites publics¶
Publier fige un instantané : asset_public (une ligne par (compte, nom) — la
place de republication) et ses frames recopiées en lignes asset_public_frame,
aux mêmes empreintes. Retoucher ou supprimer le sprite privé n'y change rien ;
republier crée une version si les frames ont changé, et ne fait que mettre à jour
la fiche si seuls la catégorie ou les tags ont bougé. Seuls sprite et anime se
publient ; licence CC0-1.0 ou CC-BY-4.0 avec son crédit, selon l'origine déclarée,
7 catégories fixes, 8 tags au
plus, 500 publications par compte. Retirer supprime la ligne (les octets
partent au balayage si plus rien ne les nomme) et journalise sans empreinte.
Copier (CopierPublic) fait un asset privé ordinaire aux mêmes empreintes,
avec son origine (asset.origine_*, le pseudo au moment de la copie) sans clé
étrangère : un retrait ou un compte supprimé ne touchent jamais une copie.
Recopier la même version rend la copie déjà faite. Un nom pris donne -2, -3…
avant le nombre de frames (torche-2_4).
Les niveaux publics¶
Publier un niveau fige un pack : le niveau, sa carte, les modèles de ses
objets et leurs sprites. Niveau, carte et modèles forment un document — JSON
compressé, déterministe ({format, niveau, carte, modeles}), sans identifiants,
dates ni projets cochés — rangé sous doc_hash ; l'aperçu est le PNG du
navigateur ; les frames des sprites sont des lignes niveau_public_asset, aux
empreintes de la bibliothèque, avec un lien sans clé vers leur publication
d'asset. Les sprites encore privés sont publiés dans la même transaction ; une
copie inchangée d'un sprite public ne l'est pas, l'original suffit. La licence est
la plus stricte de ce que le pack contient : les signalements du niveau et de sa
carte, les sources de la carte (ou « Sources not reviewed », qui bloque) et les
sprites des modèles posés ; niveau_public garde la licence et les crédits
réunis. Même règle de version que les sprites ; 100 niveaux publics par compte. Un
niveau public qu'un jeu publié emploie ne se retire pas.
Copier un niveau, cloner un jeu¶
copierNiveauTx recrée le pack chez l'autre compte, sans jamais rien écraser
et sans copier aucun produit compilé : la copie se recompile.
- elle refuse si le document a changé depuis que le client l'a lu ; la même version déjà copiée rend la copie existante ;
- les sprites : même nom et mêmes frames → réutilisé ; une copie inchangée de
la même publication → réutilisée ; sinon un nom libre (
-2…) ; - les modèles : identique → réutilisé ; sinon
_2,_3… (une clé Lua de 24 caractères) ; - la carte : copiée une fois par opération (la clé : l'empreinte de la
carte dans le document), sous un nom libre ; elle reçoit une source qui
crédite l'auteur du niveau public (
level <auteur>/<nom>, la licence de la publication, le crédit<auteur> (papyDeck)) : sans elle, republier la copie ne créditerait personne pour les tuiles ; - le niveau :
_2,_3…, libre comme niveau et comme bundle, borné pour que<niveau>/<sprite>_<n>tienne avec le plus long de ses sprites ; - les références — états vers sprites, objets vers modèles, niveau vers carte — sont réécrites ;
- niveau et modèles renommés gardent leur nom de jeu (
nom_jeu) : la compilation attacheniveau1_2sousniveau1, écritniveau1.lua, garde la cléM.modeles.joueur. Le code écrit pour l'original marche tel quel.
Cloner (POST /api/clone, {release_id}) fait, en une transaction, le projet
(slug libre -2…, quota compté sur la taille logique de la release,
parent_release_id posé), ses fichiers depuis release_file — sauf les
pièces des niveaux (.map, .atlas, .pal, .png) et credits.txt —, puis
la copie de chaque niveau de release_niveau, deux niveaux sur une même carte
partageant une seule copie de la carte. Sans ses sources, pas de clone. L'atelier
compile ensuite chaque niveau copié dans le clone : c'est ce qui l'attache. Le
clone hérite de la licence et des crédits du jeu cloné (project.licence_heritee,
credits_herites).
Le retrait pour crédit invalide¶
Un crédit faux fait retirer tout de suite ce qui est public
(POST /api/admin/credit-invalide, {quoi: sprite | niveau | jeu, id, note,
apercu}, administrateurs ; apercu rend le bilan sans rien faire). Le retrait
s'étend, au point fixe : un sprite emporte les niveaux publics qui l'embarquent,
qui emportent les jeux qui les emploient. Les copies déjà faites chez les autres ne
s'effacent pas : elles sont signalées (credit_invalide_le, _motif sur
asset, carte, niveau, project). Viser un niveau signale ce niveau, sa carte
et leurs copies ; la cascade depuis un sprite ne signale que les niveaux. Un
élément signalé reste utilisable chez son propriétaire, mais rien de ce qui le
contient ne se publie ; son propriétaire lève le signalement en cochant la case
(POST /api/signalement/lever, {quoi: sprite | carte | niveau | projet, nom,
confirme}), et c'est journalisé.
L'audio¶
Calqué sur la bibliothèque graphique, avec une différence de fond : pas de
palette partagée. Un .wav se convertit seul en .sfx (en-tête PDSX, PCM 8
bits mono 24 kHz) ; un .son est du texte (16 Kio au plus), que seul le
navigateur analyse. Rien ne se calcule sur un groupe : le bundle audio n'est
qu'un regroupement nommé.
audio: un son = un blob (sfxoumusique), avec son nombre d'échantillons ;audio_bundle+audio_bundle_piece: les sons, chacun sous le nom plat que le jeu lira ;project_audio_bundle: l'attache, une étiquette par projet.
Les pièces arrivent dans le dossier du jeu sans préfixe — snd.play("tir")
lit tir.sfx, snd.music("theme") lit theme.son —, à l'inverse des pièces
graphiques. D'où le refus à l'attache d'un nom déjà fourni par un autre bundle
audio ou par un fichier du projet.
La console n'a qu'une réserve : 64 effets et 1 920 Kio d'échantillons
(PD_SON_MAX_SFX, PD_SON_PCM_BYTES, ~82 s), tous bundles et fichiers .sfx
confondus ; les morceaux n'y comptent pas. Elle refuse le jeu entier au lancement
quand elle déborde ; l'App refuse donc avant, aux quatre endroits où l'audio d'un
projet grossit : le dépôt d'un .sfx ou .wav dans le projet, l'enregistrement
d'un bundle (seul, il doit déjà tenir), l'attache, la publication.
Les noms, et leur borne¶
Une seule famille de règles, celle de la carte micro-SD : une release se pose
telle quelle sur du FAT32, qui ne distingue pas la casse. Donc minuscules
partout, pas de nom réservé de Windows (con, nul, com1…), des noms plats
comme require().
| Nom | Règle |
|---|---|
| slug de projet | [a-z0-9][a-z0-9_-]{0,62} |
| fichier de projet | [a-z0-9_-]{1,60} + .lua, .mask, .son (créés) ou .sfx (déposé) |
| sprite, son | [a-z0-9_-]{1,60} ; un anime finit par _<frames> |
| niveau, carte, bundle audio, variante | [a-z0-9_]{1,60} — pas de tiret : il sépare le groupe de son fond (V-fond) ; un niveau est libre comme niveau et comme bundle |
| modèle, état | [a-z][a-z0-9_]{0,23} — une clé Lua (M.modeles.joueur) |
| calque, décor | [a-z0-9_]{1,24} |
| propriété d'objet | [a-z_][a-z0-9_]{0,23}, valeur de 64 caractères au plus |
| tag public | [a-z0-9_-]{1,24}, 8 au plus |
Un nom complet — V/<région> pour gfx.id(), V-fond pour
gfx.background(), V.<variante> — tient dans les tampons de 48 octets de la
console et du simulateur (PD_NAME_MAX du core, PDC_NAME_MAX de RP1, alignés
début septembre 2026 après qu'une troncature silencieuse eut rendu gfx.id()
muet sur la carte) : 47 caractères au plus. Ce nom n'existe pas avant d'être
attaché ; il se vérifie donc à la compilation, contre le nom vu et contre
chaque attache existante du bundle, avec un refus qui nomme le fautif. Chaque
moitié garde sa propre règle ; aucune paire qui tient n'est interdite. Les copies
s'y plient d'avance : un sprite copié est borné pour tenir derrière un niveau
d'une lettre, un niveau copié pour tenir avec son plus long sprite.
La surface de l'API¶
Tout répond en JSON sous /api/, avec de vrais codes HTTP ; un refus porte
{error, detail} (la compilation, elle, rend son compte rendu avec refus). La cloison tient en une phrase : /api/projects/** et ce qui
appartient au compte exigent la session et se résolvent toujours par (compte,
nom) ; la vitrine se lit sans compte.
| Famille | Routes | Ce qu'elles font |
|---|---|---|
| Session | GET /api/session |
qui est là, ce que la console tolère |
| Projets | /api/projects, /api/projects/{slug} |
lister, créer (avec main.lua), lire (fichiers et pièces), supprimer |
| Fichiers | …/{slug}/files, …/files/{nom}, …/upload |
créer, lire, modifier sous If-Match, supprimer, déposer |
| Attaches | DELETE …/{slug}/bundles/{V}, POST …/{slug}/audio, DELETE …/{slug}/audio/{V} |
détacher un niveau ; attacher et détacher un bundle audio |
| Versions | …/{slug}/releases, …/{slug}/publish, …/{slug}/retirer |
l'état des versions, publier, retirer le jeu |
| Cartes | /api/cartes, /api/cartes/{nom} |
lister (avec les niveaux qui les posent), créer, lire, enregistrer sous If-Match, supprimer (409 si des niveaux la posent) |
| Niveaux | /api/niveaux, /api/niveaux/{nom}, …/compiler |
lister, créer sur une carte, lire, enregistrer objets et réglages sous If-Match, compiler, supprimer |
| Modèles | /api/modeles, /api/modeles/{nom} |
lire, créer ou remplacer, supprimer |
| Bibliothèque | /api/assets, /api/assets/{nom}, …/origine, …/publier |
déposer (le redépôt remplace), lire, déclarer l'origine, supprimer, publier |
| Sprites publics | /api/publics, /api/publics/{id}, …/copier, …/retirer |
chercher, lire, copier, retirer |
| Niveaux publics | /api/niveaux/{nom}/publication, …/publier, /api/niveaux-publics, …/{id}, …/copier, …/retirer |
situation et publication d'un niveau ; chercher, lire, copier, retirer |
| Audio | /api/audio, /api/audio/musiques, /api/audio/{nom}, …/origine, /api/audio-bundles/{nom} |
sons, morceaux (texte), leur origine, bundles audio |
| Crédits | /api/admin/credit-invalide, /api/signalement/lever |
retirer pour crédit invalide (administrateurs), lever un signalement |
| Vitrine (sans compte) | /api/gallery, /api/releases/{id}, …/files/{nom}, /api/firmware |
les jeux publiés, leurs fichiers, les firmwares |
| Clone | POST /api/clone |
un projet depuis une release, sources comprises |
Hors API ne restent que les routes qui servent des octets — les frames
(/gfx/assets/…, /gfx/publics/…, et leur anim.gif), les sons
(/audio/assets/{nom}), l'aperçu d'un niveau public, l'export .zip d'un
projet —, la connexion, la page du compte, l'administration, l'atelier lui-même
(/app2/) et le simulateur (/sim/). Jusqu'en octobre 2026, des pages HTML
rendues par le serveur écrivaient les mêmes tables par des formulaires ; leurs
règles sont restées, partagées avec l'API (« deux portes, une règle ») ; les
pages, elles, ont disparu.