Docs → Étape 5
Étape 5 — Validation du moteur local
Comparaison pixel à pixel avec l'API officielle. Pas de « ça a l'air bon » : des écarts chiffrés, canal par canal.
1. Protocole
Pour chaque cas : le gabarit archivé est recolorisé localement, la même combinaison est demandée à l'API, et les deux PNG sont comparés pixel par pixel et canal par canal. Six cas — cinq couleurs réalistes, plus un cas volontairement pathologique.
Les 8 pixels de la bande de calibration sont comptés à part : ce sont des métadonnées du moteur, pas des pixels de vêtement.
2. Résultats
| Cas | Couleurs | Px | Écart moyen | Écart max | Alpha |
|---|---|---|---|---|---|
top/basic | #3366cc / #ffcc00 | 460 | 0,0761 | 1 | 0 |
top/chef_shirt | #2e8b57 / #daa520 | 822 | 0,0073 | 1 | 0 |
bottom/jeans | #4682b4 / #b22222 | 400 | 0,1583 | 1 | 0 |
headwear/cap | #7f3fbf / #cc6633 | 190 | 0,1825 | 1 | 0 |
outerwear/alien_force | #b0c4de / #556b2f | 830 | 0,3068 | 1 | 0 |
top/basic dégénéré | #ff0000 / #0000ff | 460 | 0,0558 | 1 | 0 |
6 cas sur 6 conformes à 1 LSB près, soit 1/255 — l'écart d'arrondi irréductible entre deux implémentations. Aucun désaccord sur le canal alpha, sur aucun cas. Le moteur reconstruit reproduit fidèlement celui de l'API.
3. Le cas dégénéré, et où l'écart se loge
Le sixième cas utilise #ff0000 et #0000ff — deux couleurs ayant
exactement deux canaux à zéro. Le moteur officiel y applique parfois une rampe plus
resserrée. Décomposition de l'écart :
| Région | Pixels | Écart max |
|---|---|---|
| Vêtement | 460 | 1 |
| Bande de calibration | 8 | 19 |
La totalité de l'écart se concentre sur les 8 pixels de la bande de calibration, dans le coin inutilisé du skin. Le vêtement lui-même reste exact à 1 LSB même avec une couleur saturée. Un utilisateur ne verrait jamais la différence.
4. Ce que j'ai refusé de coder
En caractérisant la rampe, j'ai relevé une asymétrie que je n'ai pas su expliquer. Le ratio du slot le plus sombre, mesuré par canal et par intensité :
| Canal | 64 | 128 | 192 | 255 |
|---|---|---|---|---|
| Rouge seul | 0,750 | 0,750 | 0,823 | 0,824 |
| Vert seul | 0,750 | 0,820 | 0,823 | 0,824 |
| Bleu seul | 0,750 | 0,750 | 0,750 | — |
| Gris | 0,828 | 0,820 | 0,823 | 0,824 |
Le vert à 128 suit la rampe standard, le rouge et le bleu non ; à 192 le bleu diverge toujours mais plus le rouge. Ni HSL, ni HSV, ni une pondération de luma, ni une multiplication en lumière linéaire ne rendent compte de ce comportement — j'ai testé les quatre, aucun ne colle.
J'avais d'abord codé une heuristique « si deux canaux sont nuls, rampe resserrée ».
Elle aggravait le résultat : l'écart moyen passait de 0,08 à 2,21,
parce qu'elle se déclenchait sur #ff0000 qui, lui, suit la rampe standard.
Je l'ai retirée. Une heuristique dont on ne comprend pas le domaine de validité est
pire qu'une limite documentée.
5. Questions ouvertes
- La règle exacte qui déclenche la rampe resserrée. Elle dépend à la fois du canal et de l'intensité, ce qui évoque un artefact d'implémentation — peut-être un accrochage sur une palette pour les couleurs très saturées.
- Le saut de +35 entre le niveau de base et le premier niveau clair, alors que tous les autres pas côté clair valent +18 : un neuvième slot existe probablement dans l'outillage de l'auteur sans être utilisé dans les textures publiées.
- Aucun layer à plus de deux zones n'a été trouvé sur les 374 variantes. La structure du
manifest ne prévoit d'ailleurs que
_coloret_color_secondary.
6. Le moteur, en entier
Une fois le gabarit extrait, la recolorisation tient en quelques lignes. C'est tout ce que fait la page de démo :
const RATIOS = [0.823516, 0.874117, 0.929377, 1.0,
1.136881, 1.206932, 1.277318, 1.347848];
for (chaque pixel non transparent) {
const zone = template.R; // 0 fixe, 1 primaire, 2 secondaire
const slot = template.G; // 0..7
if (zone === 0) { recopier la couleur fixe; continue; }
const c = (zone === 1) ? couleurPrimaire : couleurSecondaire;
for (canal de 0 à 2)
sortie[canal] = clamp(round(c[canal] * RATIOS[slot]), 0, 255);
sortie.alpha = template.alpha; // jamais modifié
}
Huit multiplications et un arrondi. Toute la difficulté était de reconstituer le gabarit, pas d'appliquer la couleur.