Skip to content

Variation visuelle

BodyVariation est l'identité visuelle d'un corps — un sac de ~50 valeurs (seeds, multiplicateurs, opacités, couleurs aléatoires) calculées déterministement depuis le BodyConfig.name et consommées par les shaders. C'est le pendant visuel de BodyConfig : là où ce dernier dit « ce corps a-t-il un océan, des fissures, des anneaux », BodyVariation dit « exactement à quoi ils ressemblent ».

Pourquoi deux concepts séparés ?

Le commentaire dans render/body/bodyVariation.ts résume le partage :

Physics (BodyConfig) sets whether features exist and their maximum values.Variation sets the exact visual expression within those constraints.

Concrètement :

DécisionLieuExemple
« Ce corps a-t-il des fissures ? »BodyConfig (hasCracks: true)Game logic / catalogue
« Avec quelle largeur, quelle échelle, quel mode de blend ? »BodyVariationSeedé depuis name
« Quel nuage de bandes (Saturne / Jupiter / ...) ? »BodyConfig.bandColorsPalette caller-resolved
« Quelle nuance précise dans le warm/cool, quelle luminance ? »BodyVariation.gasColorMix, gasLuminanceSeedé

Cette séparation garantit que deux corps avec le même name rendent identiquement sur n'importe quelle machine — c'est le contrat de déterminisme de la lib.

Génération automatique

useBody(config, tileSize) appelle generateBodyVariation(config) en interne. Vous n'avez rien à faire pour qu'un corps ait une identité visuelle.

ts
const body = useBody(config, DEFAULT_TILE_SIZE)
console.log(body.variation.gasBandSharpness) // 0.10–0.65, déterministe

Override avant useBody

Trois patterns courants pour remplacer une partie de la variation :

1. Variation pré-calculée (cache)

generateBodyVariation est pure — vous pouvez la cacher par name (cf. Performance §6) et la passer en option :

ts
import { useBody, generateBodyVariation } from '@cedric-pouilleux/stellexjs/core'

const variation = generateBodyVariation(config)
const body = useBody(config, DEFAULT_TILE_SIZE, { variation })

2. Override partiel (mutate then build)

ts
const variation = generateBodyVariation(config)

// On force un look « volcanique » indépendant du seed
variation.lavaIntensity = 0.7
variation.lavaColor     = '#ff5520'
variation.crackIntensity = 0.5

const body = useBody(config, DEFAULT_TILE_SIZE, { variation })

3. Override d'anneau spécifiquement

ts
import { ARCHETYPE_PROFILES } from '@cedric-pouilleux/stellexjs/core'

const variation = generateBodyVariation(config)
if (variation.rings) {
  variation.rings = {
    ...variation.rings,
    archetype: 'shepherd',
    profile:   ARCHETYPE_PROFILES.shepherd,
  }
}
const body = useBody(config, DEFAULT_TILE_SIZE, { variation })

Le piège « features 0 par défaut »

Les fissures et la lave partent à crackIntensity = 0 / lavaIntensity = 0. Pousser uniquement hasCracks: true ou hasLava: true dans BodyConfig ne suffit pas à les voir — ce sont des flags d'autorisation, pas d'activation. La doctrine est explicite : la lib n'a pas de modèle qui décide quand afficher des fissures (température ? âge ? activité tectonique ?), donc le caller doit pousser une intensité non nulle quand son gameplay le décide.

ts
// ❌ Ne suffit pas — pas de fissures visibles
useBody({ ..., hasCracks: true }, DEFAULT_TILE_SIZE)

// ✅ Avec intensité poussée par le caller
const variation = generateBodyVariation(config)
variation.crackIntensity = 0.6
useBody(config, DEFAULT_TILE_SIZE, { variation })

// ✅ Ou en runtime sur le matériau
body.planetMaterial.setParams({ crackAmount: 0.6 })

Pareil pour la lave (lavaIntensitylavaAmount shader).

Pourquoi cette friction ?

La lib pourrait pousser un crackIntensity aléatoire par défaut, mais ça créerait deux problèmes : (1) hasCracks: true deviendrait visuel alors que c'est censé être un gate de capacité, (2) un caller qui veut un look propre devrait pusher 0 explicitement. Le défaut « éteint » respecte l'invariant « un corps qui n'opte pas dans une fonctionnalité ne la verra pas ».

Catalogue des champs

Bruit partagé (toutes planètes)

ChampPlageEffet
noiseSeed[-50..50]³Décalage du domaine simplex — change la « personnalité » du relief
noiseFreq0.65–1.55Multiplicateur de fréquence sur le bruit du shader

Terrain (rocky / metallic)

ChampPlageEffet
roughnessMod0.6–1.4Multiplicateur sur la rugosité PBR
heightMod0.5–1.5Multiplicateur sur l'amplitude du relief
craterDensityMod0.3–1.7Multiplicateur sur la densité des cratères
craterCountMod0.4–1.6Multiplicateur sur le nombre de cratères
waveAmount0.0–1.0Couche vagues (indépendant de la physique)
waveScale0.5–2.5Échelle XY des vagues
colorMix0–1Shift warm/cool des colorA/colorB
luminance0.8–1.2Brillance globale de la palette

Fissures (rocky / metallic)

ChampPlageEffet
crackIntensity0–1Défaut 0 — caller pousse pour activer
crackWidthrocky 0.10–0.50 / metal 0.10–0.40Largeur
crackScalerocky 1.0–4.0 / metal 1.6–5.0Échelle spatiale
crackDepth0.5–1.0Profondeur visuelle
crackColorhexSur rocky, recomputé depuis les ancres terrain
crackBlend0–4 (rocky) / 0 ou 4 (metal)Mode de fusion

Lave (rocky / metallic)

ChampPlageEffet
lavaIntensity0–1Défaut 0 — caller pousse pour activer
lavaEmissive0.8–2.8Intensité d'émission
lavaScale0.3–2.5 (rocky) / 0.3–1.0 (metal)Largeur des canaux
lavaWidth0.02–0.30Finesse des filaments
lavaColorhexDéfaut neutre #cc2200, caller override pour climat

Surface métallique

ChampPlageEffet
metalness0–1Métalicité PBR — variation oxydation / pureté de surface

Géante gazeuse — bandes

ChampPlageEffet
gasBandSharpness0.10–0.65Netteté des transitions de bandes
gasBandWarp0.05–0.55Déformation sinusoïdale
gasJetStream0.10–0.90Intensité des courants équatoriaux
gasTurbulence0.10–0.90Turbulence générale
gasCloudDetail0.10–0.75Détail des masses nuageuses

Géante gazeuse — couleurs et nuages

ChampPlageEffet
gasColorMix0–1Shift warm/cool dans le preset
gasLuminance0.7–1.3Brillance globale
gasCloudAmount0.0–0.65Opacité de la couche nuageuse haute
gasCloudColorhexTint nuage (range neutre #d0b890#f0e0c0)

Anneaux

ChampTypeEffet
ringsRingVariation | nullnull quand hasRings !== true ; sinon archétype + profil + couleurs + opacité

Cf. Anneaux simples et Archétypes pour le détail de RingVariation.

Garantie de stabilité de la séquence PRNG

Le générateur tire toujours les mêmes échantillons dans le même ordre, même quand hasRings est false ou quand la stratégie active n'override pas solVariationRanges. Cette discipline garantit que :

  • ajouter ou retirer hasRings: true ne décale pas les couleurs / cracks / lave d'un body (les valeurs ring sont toujours tirées),
  • le DEFAULT_LAVA_COLOR est posé sur les non-métalliques même quand lavaIntensity = 0 — la séquence reste déterministe.

Si vous étendez bodyVariation.ts, ajoutez vos rng() à la fin : insérer un appel au milieu décalerait toute la suite et casserait la rétro-compatibilité visuelle des bodies déjà nommés.

Voir aussi

Distribué sous la licence indiquée dans le dépôt.