Skip to content
Implementation

Noyau & coquilles

Tout corps non-stellaire est composé d'un noyau solide central et d'une coquille (sol + atmo) qui l'entoure. Le ratio entre les deux est piloté par coreRadiusRatio, ou dérivé d'une gasMassFraction via deriveCoreRadiusRatio.

Le noyau est rendu par buildCoreMesh — une sphère animée (shader procédural + lumière pulsante) qui sert de fond à toute excavation. Tant que le sol n'est pas creusé, le noyau reste invisible (occulté par les prismes hexagonaux). Pour le voir, il faut miner des tuiles jusqu'à elevation = 0 et regarder à travers le trou.

Une dizaine de tuiles ont été pré-excavées au mount pour exposer le noyau. Cliquez d'autres tuiles pour creuser plus — la flamme et la lumière qui s'en échappent appartiennent au mesh du noyau.

Anatomie

                ┌──────────────────────────┐
                │   atmosphère (shell)     │ ← bandes empilées
                │   sol (terrain hex)      │   au-dessus du noyau
        ┌───────┴──────────────────────────┴───┐
        │           NOYAU OPAQUE              │ ← buildCoreMesh
        │   (radius × coreRadiusRatio)        │   sphère animée
        └─────────────────────────────────────┘
CoucheModuleRôle
NoyaubuildCoreMeshSphère opaque inner — visible par les tuiles minées (band 0)
Sol (hex)buildLayeredInteractiveMesh (sol band)Terrain hexagonal au-dessus du noyau
Atmo (hex)buildLayeredInteractiveMesh (atmo band)Coquille atmosphérique
Smooth fallbackbuildSmoothSphereMeshSphère lisse procédurale, affichée en mode shader ou en vue atmosphère

Précision sur la vue atmosphère

body.view.set('atmosphere') masque le sol mais affiche également la smooth sphere de fallback à la surface — donc le noyau reste invisible. La seule façon de voir le noyau est la vue surface (mode hex) avec des tuiles minées à band 0.

Trois façons de fixer le ratio

ts
import { resolveCoreRadiusRatio, deriveCoreRadiusRatio, DEFAULT_CORE_RADIUS_RATIO } from '@cedric-pouilleux/stellexjs/core'

L'ordre de priorité dans resolveCoreRadiusRatio(config) :

  1. Override expliciteconfig.coreRadiusRatio (entre 0 et 1)
  2. Dérivation physiquederiveCoreRadiusRatio(config.gasMassFraction)
  3. DéfautDEFAULT_CORE_RADIUS_RATIO (0.55)
ts
// 1. Override direct — taille du noyau pilotée à la main
useBody({ type: 'planetary', surfaceLook: 'terrain', name: 'a', coreRadiusRatio: 0.85, /* … */ })

// 2. Dérivé d'une fraction massique d'enveloppe gazeuse
useBody({ type: 'planetary', surfaceLook: 'bands', name: 'b', gasMassFraction: 0.9, /* … */ })

// 3. Défaut (0.55)
useBody({ type: 'planetary', surfaceLook: 'terrain', name: 'c', /* … */ })

Dérivation depuis gasMassFraction

deriveCoreRadiusRatio(f) résout un partage de volume bi-phase à partir de deux densités de référence exposées par la lib :

ts
REF_SOLID_DENSITY = 5500  // kg/m³
REF_GAS_DENSITY   =  100  // kg/m³

Et calcule :

V_solid / V_total = (1 - f) / ((1 - f) + f · ρ_solid / ρ_gas)
coreRadiusRatio   = ∛(V_solid / V_total)

Cas limites :

gasMassFractioncoreRadiusRatioForme
01.00Solide pur — pas d'enveloppe gazeuse
0.5~0.40Coquille épaisse, noyau intermédiaire
0.9~0.22Petit noyau, grosse coquille
10.00Pas de noyau — buildCoreMesh skip

Vous pouvez aussi spécifier coreRadiusRatio directement et ignorer la dérivation.

Conséquence sur la simulation

Le ratio noyau/coquille détermine le nombre de bandes d'élévation via resolveTerrainLevelCount — la lib calcule en interne round(shell / step)shell = (1 - coreRadiusRatio) × radius et step est anchored sur DEFAULT_TILE_SIZE. Le résultat est borné à un minimum interne (4 bandes) pour garantir une staircase utilisable même sur les coquilles très fines.

Plus le noyau est gros, plus la coquille est mince, moins il y a de bandes d'élévation. Un corps avec coreRadiusRatio: 0.85 aura peut-être 4 bandes seulement (le minimum), tandis qu'un corps à coquille épaisse (0.40) en aura ~8.

C'est aussi ce qui borne la profondeur d'excavation : creuser une tuile descend par paliers de terrainBandLayout(...).unit jusqu'à révéler le noyau à elevation = 0.

Excaver pour voir le noyau

ts
if (body.kind !== 'planet') return
// Creuser une tuile jusqu'au noyau (elev 0)
body.tiles.sol.updateTileSolHeight(new Map([[tileId, 0]]))

// Creuser un cratère de plusieurs tuiles
import { buildNeighborMap, getNeighbors } from '@cedric-pouilleux/stellexjs/sim'

const nMap   = buildNeighborMap(body.sim.tiles)
const queue  = [startTileId]
const updates = new Map<number, number>()
while (queue.length && updates.size < 12) {
  const id = queue.shift()!
  if (updates.has(id)) continue
  updates.set(id, 0)
  for (const n of getNeighbors(id, nMap)) queue.push(n)
}
body.tiles.sol.updateTileSolHeight(updates)

Le noyau émet sa propre lumière (PointLight parentée au mesh) qui ne passe à travers les tuiles que là où le sol est creusé.

API exposée

SymboleModuleRôle
DEFAULT_CORE_RADIUS_RATIOphysics/bodyConstante 0.55
REF_SOLID_DENSITYphysics/body5500 kg/m³
REF_GAS_DENSITYphysics/body100 kg/m³
deriveCoreRadiusRatio(f)physics/bodygasMassFraction → ratio (pure)
resolveCoreRadiusRatio(cfg)physics/bodyÉchelle de priorité (override → dérivé → défaut)
buildCoreMesh({ radius, coreRadiusRatio })render/shells/buildCoreMeshMesh sphère inner
body.tiles.sol.updateTileSolHeight(map)PlanetBody handleMute la hauteur sol des tuiles (0 = expose le noyau)
body.getCoreRadius()Body handleRayon monde du noyau
body.getSurfaceRadius()Body handleRayon monde de la surface (= config.radius)

Cas particulier : pas de noyau

Quand coreRadiusRatio = 0 (équivalent gasMassFraction = 1), buildCoreMesh détecte le cas et skip la création du mesh. Le rendu se réduit à : sphère smooth procédurale + shell atmosphère + (optionnel) anneau.

Anatomie d'une géante 100 % gaz

ts
const jovian = useBody({
  type:           'planetary',
  surfaceLook:    'bands',
  name:           'PureJovian',
  radius:          1.5,
  rotationSpeed:   0.003,
  axialTilt:       0.18,
  gasMassFraction: 1,                  // → coreRadiusRatio = 0
  atmosphereThickness: 0.6,
}, DEFAULT_TILE_SIZE)

À ce point :

  • buildCoreMesh retourne un mesh placeholder vide — pas de sphère opaque inner, pas de PointLight de noyau, zéro draw call.
  • La sol band collapse — sa hauteur radiale ((1 - coreRatio - atmoThickness) × radius) tombe sous MIN_SOL_BAND_FRACTION × radius (5 %), donc la lib ré-injecte la guard pour préserver une sliver de sol. En pratique, un body avec gasMassFraction = 1 et atmosphereThickness ≥ 0.95 aura un sol pratiquement inexistant.
  • L'atmo shell occupe presque toute la silhouette visibleatmoOuterRadius = radius, atmoInnerRadius ≈ radius × (1 - atmoThickness).
  • Aucune profondeur d'excavation utileupdateTileSolHeight(map) reste utilisable mais la sol band est trop fine pour produire un effet visible.

C'est le pattern à privilégier pour Jupiter, Neptune, et toute géante dont vous ne voulez pas que le noyau apparaisse à l'excavation. La distinction d'avec un gasMassFraction = 0.93 (Jupiter réaliste) :

gasMassFraction: 0.93gasMassFraction: 1
coreRadiusRatio résolu~0.200.0
Mesh noyauPetit, visible si excavationSkipped
Cas pure-gas testable en gameplayOui (mining lent jusqu'au noyau)Non
Coût GPU+1 sphère + 1 lightréférence

Toggle « pas d'atmo » pareil ?

Symétrique : atmosphereThickness = 0 (ou hasAtmosphere(config) === false) skip le shell atmo, le atmoBoardMesh (atmosphère cliquable) et le mesh atmo de la LayeredInteractive. Une planète tellurique sèche minimale (sans atmo, sans liquide) ne mount donc que le noyau + le sol hex + (option) un anneau.

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