Coming soon toSteam
Modding docsAPI 1

Planets

Example: Planet System - the planet Ember with a heightmap, ground layers, a sea, clouds and stormy weather, and its airless moon Cinder added from a script.

How it works

A map can bring its own planets instead of using one of the game's worlds. You describe them as data - in the map file's "system" section, or from a script while the mod loads - and every player's game builds the same planets from that description. Nothing about a planet is sent over the network; it does not have to be, because the server and every player have the same mod files (the game checks them) and the same description (the game checks that too, see Multiplayer).

Put the map on the Empty base. A map with a "system" then loads Void, empty space with a sun, and the game builds your planets into it. (A game version without the Void scene loads Flat Planet and hides its planet instead; the result is the same.) On another base (Earth, ...) your planets are added and the base's own planets are hidden, unless you set "replaceBasePlanets": false.

content/maps/twin-worlds.json
{
  "id": "twin-worlds",
  "name": "Twin Worlds",
  "base": "Empty",
  "spawns": [ { "position": [0, 900, 0] } ],
  "system": {
    "sun": { "color": "#fff1dc", "intensity": 1.0, "pitch": 35, "yaw": 140 },
    "weather": "stormy",
    "bodies": [
      {
        "name": "Ember",
        "radius": 40000,
        "position": [0, -40000, 0],
        "gravity": 7.5,
        "atmosphere": { "height": 6000 },
        "sea": { "level": 0 },
        "terrain": {
          "heightmap": "/textures/ember-height.png",
          "heightRange": 1600,
          "midpoint": 0.45,
          "ground": { "default": "dirt", "rockSlope": 36 },
          "biomes": [
            { "textures": [
              { "albedo": "/textures/grass.png", "ground": "grass", "maxSlope": 28 },
              { "albedo": "/textures/rock.png", "ground": "rock", "minSlope": 30 }
            ] }
          ]
        },
        "clouds": { "bottom": 900, "top": 2700, "coverage": "/textures/clouds.png" }
      }
    ]
  }
}

Positions are absolute world metres: a planet of radius 40 000 m centred at [0, -40000, 0] has its top at the world's origin. Put the spawn points above the ground there (a missing spawn defaults to just above the top of the first planet). Paths starting with / are from your mod's folder.

system

FieldMeaning
bodiesThe planets, 1 to 8. Required.
suncolor, intensity (0-20), pitch (degrees above the horizon) and yaw. Left out: the base scene's sun.
weatherA preset - "temperate", "arid", "stormy", "frozen", "clear" - or { "preset": ..., fields } with any of enabled, mode (auto/clear/rain/snow/storm), forcedIntensity, hoursAhead, stormCount, fairSpells, stormSize, wetness, windScale, temperatureOffset, equatorTemperature, poleTemperature, lapseRate, minimumAirDensity. The server applies it for everyone when the map starts and puts the previous weather back when the map is left. Left out: the server's weather settings stay as they are.
replaceBasePlanetstrue (default) hides the base map's own planets. Always true on Empty/Void.

Weather is one setting for the whole world (every planet with air has storms worked out from it, each planet differently because its name seeds them), so it belongs to the system, not to a planet.

A body

FieldMeaning
nameUnique, 1-48 letters, digits, spaces and - _ . '. It also seeds the planet's weather, so renaming a planet changes its storms.
radiusMetres, 1 000 to 10 000 000 (Earth's map is 637 100).
positionCentre, [x, y, z], absolute metres. Required. Planets (with their air) must not overlap.
rotation[x, y, z] degrees. Left out: a tilt chosen so the top of the planet is not on a seam of the terrain mesh.
gravitySurface gravity, m/s² (default 9.81).
gravityReachWhere gravity has faded to nothing, in planet radii (default 3, 0 = never).
atmosphere{ height, density, falloff, rayleigh, mie } - air height in metres, surface density kg/m³ (1.225 = Earth), how fast it thins (0.5-10), the sky's colour ("#3a6bff") and haze strength - or false for no air and no sky. Left out: Earth-like air 2 % of the radius high.
sea{ "level": metres } above the radius, true (level 0), or left out for no sea. Only one planet per map has a sea you can float on; on others it is drawn only.
terrainSee below.
clouds{ bottom, top, density, coverage } - metres above the radius and a cover picture (grey PNG, equirectangular, white = cloud) - true for defaults, or left out for none. Needs air.
rings, fieldsNot built by this version yet (a warning says so).

terrain

FieldMeaning
heightmapA grey PNG covering the whole planet in equirectangular projection (2:1, left edge = right edge). Any PNG works - 8 or 16 bits, colour or grey (the red channel is used); the game converts it to the 16-bit format the terrain needs. 16-bit PNGs give smoother slopes.
heightRange, midpointThe ground is heightRange × (pixel − midpoint) metres above the radius (pixel 0..1). Sea level is the radius plus sea.level.
strataTerracing of the relief (0-10, default 1).
textureSize256, 512, 1024 (default) or 2048: every ground texture and layer heightmap is resized to this square size.
groundThe physics of the ground where no texture layer says otherwise: default kind, rockSlope (degrees above which it is rock, 90 = never), snowAltitude (metres, 0 = no snow line), shoreHeight (metres of beach above the sea).
biomesUp to 4. Each has an optional mask (grey equirectangular PNG, white = this biome; maskInvert, maskSharpness), up to 4 layers of extra relief (heightmap tiled every globalSize metres, range, midpoint, strata) and up to 8 textures (16 per planet).

Texture layers

{ "albedo": "/textures/rock.png", "ground": "rock", "minSlope": 30, "minSlopeFalloff": 6, "height": "/textures/rock-height.png", "heightMetres": 0.2 }

albedo is a colour PNG that tiles. Rules decide where it is painted: minHeight/maxHeight (metres above the radius) and minSlope/maxSlope (degrees), each with a ...Falloff; a later layer paints over earlier ones. height is an optional grey relief picture for close-up depth (heightMetres deep).

ground says what the layer IS for wheels, tracks and digging: dirt, grass, mud, sand, rock, gravel, snow, pavement, bog, or default (the planet's ground rules decide). The game never guesses from a file name for mod planets, so always set it - a layer without it gets a warning.

From a script

planets.system(map, spec) sets a map's whole system and planets.define(map, body) adds one planet to it (to the file's system, or to the one planets.system set). They take the same fields as the file, with the same checks, and work only while your scripts first run - the world is decided before the map is built:

scripts/shared.lua
planets.define("twin-worlds", {
  name = "Cinder", radius = 9000, position = { 0, 61000, 52000 }, gravity = 1.6, atmosphere = false,
  terrain = { ground = { default = "gravel" } },
})

Call them from a shared script ("entry": { "shared": "scripts/shared.lua" } in mod.json) so the server and every player run them, and only with values every machine computes the same way - never math.random, the clock or a player's own settings. Paths in scripts are from the mod's folder.

At run time

FunctionSideReturns
planets.list()both{ name, radius, position, gravity, gravityReach, atmosphereHeight, airDensity } for every planet of the current map
planets.nearest(position)boththe planet whose surface is nearest, or nil
planets.gravityAt(position)bothgravity there, m/s² (a vector pointing down)
planets.airAt(position)bothair density there, kg/m³
planets.weather(nameOrPosition)both{ planet, rain, snow, temperature, cloud, wind } or nil
planets.setWeather(presetOrTable) / trySetWeatherserver, world permissionchanges the weather for everyone until the map is left
planets.resetWeather()server, world permissionthe weather from before the map

Multiplayer

The server sends each joining player the identity of the map's planets: a hash of the system (in a fixed text form, so the order of fields and how numbers are written do not matter), the base scene it was built on and the mod's content hash (which already covers every picture). A player whose game built something different - an older version of the mod, a script that set the system on the server only, a different game version - is disconnected with a message rather than left standing on ground the server does not have.

What a mod can and cannot change

You can choose: how many planets (up to 8), their size, place, tilt, gravity and its reach, air (height, density, thinning, sky colour, haze), a sea level, the relief (a whole-planet heightmap plus tiled relief layers in up to 4 masked biomes), painted ground with its rules and its physical kind, the ground rules (default kind, rock slope, snow line, beach), clouds (height, density, cover picture), the sun's colour, brightness and direction, and the world's weather.

Not (yet): rings and asteroid fields, orbits or spinning planets (planets stand still - a moving planet would move its ground under every creation), normal maps for ground textures, caves, grass, trees and rocks scattered on the ground, cloud shapes beyond the cover picture, the materials and look of sky, sea and terrain (they come from the game's template planet), a sea you can float on on more than one planet, and weather per planet.