# Moons, planets and stars in your sky (world.sky_bodies) ## What it is for A sky with two moons, a ringed giant hanging over the horizon or a bright wandering star makes a world feel like another place. `world.sky_bodies` keeps the bodies of that sky: where each one is, whether it is up, its phase, how much of it shows and how much light it brings at night. Bodies move on simple orbits (a period, an inclination, an offset) or stay fixed in the sky. The kit follows the clock of `world.daynight` when it is installed, else it holds the sky at a configured hour. Its presentation module draws the sky from what it publishes. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "world.sky_bodies" } gripforge_gamekit_configure { project_id: "gkp_…", id: "world.sky_bodies", config: { "bodies": [{ "id": "moon", "angularSize": 2.5, "motion": { "periodDays": 12 } }] } } ``` It requires `game.engine`. Install `world.daynight` too for a sky that moves with the hours. ## First example With `world.daynight` and the config ```json { "latitude": 30, "bodies": [{ "id": "moon", "motion": { "mode": "orbit", "periodDays": 8, "phaseOffset": 0.25 } }] } ``` on day 3 at midnight the moon has made a quarter of a turn more since the start: it is full, opposite the sun. It culminates at 60 degrees toward +Z, `illuminated` is 1 and `phase` 0.5. At noon of the same day it is below the horizon. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `bodies` | list (0 to 16) | one moon | the bodies, see below | | `stars` | object | `{ density: 0.5, brightness: 1, rotate: true, day: 0, band: { intensity: 0.35, tilt: 60, node: 0 } }` | the star field: how many, how bright, turning with the hour, how much shows by day (`day`), a band of more stars along a great circle (`band`) | | `visibility` | object | `{ driver: "", phases: [{ at: 0, day: 1, night: 1 }], smoothing: 6, twilight: 6, horizonFade: 2 }` | how much the sky lets through | | `nightLights` | object | `{ max: 2, shadows: true, scale: 0.25, horizonFade: 6, saturation: 0.5 }` | the night lights of the moons: how many at most, whether the brightest casts shadows, how bright a full moon of `nightLight` 1 is (in the units of the sun of `world.lighting`), the degrees over which they fade in above the horizon, how much of the colour of the body they keep | | `latitude` | number -90 to 90 | `18` | latitude of the observer in degrees: how high the paths climb (18 puts the noon sun at 72 degrees, as `world.daynight` draws it) | | `axialTilt` | number 0 to 90 | `0` | tilt of the orbital plane on the equator, in degrees | | `hour` | number 0 to 24 | `0` | the hour of the sky when `world.daynight` is not installed | A body: `id` (`a-z`, `0-9`, `_`), `label`, `kind` (`moon`, `planet`, `star`), `angularSize` (apparent diameter in degrees), `color`, `motion`, `lit` (lit by the sun, so it has phases; `false` for a star by default), `rings` (`{ inner, outer, tilt, color, opacity }`, radii of the body), `glow` (0 to 1), `nightLight` (light at night when full, 0 by default), `appearance` (`{ craters, bands, bandColor, patches, patchColor, streaks, streakColor, spots, spotColor, albedoVariation, seed }` for a procedural surface), `textureSlot` (a binding of the `body_texture` slot instead) and `hidden`. `motion` is `{ mode: "orbit", periodDays, phaseOffset, inclination, node }` (days between two new phases; where it is at the start of day 1 in turns from the sun, 0.5 being full; degrees) or `{ mode: "fixed", azimuth, elevation }`. The `driver` of `visibility` is the path of any numeric variable (an atmosphere that thickens, a corruption that spreads). The `phases` give the visibility by day and by night along it, blended between neighbours; without a driver the first phase holds. `smoothing` is the seconds it takes to catch up with a jump, `twilight` the degrees of sun height over which day turns into night, `horizonFade` the degrees over which a body fades in above the horizon. Directions use the frame of the sun of `world.lighting`: +Y up, azimuth 0 toward +Z, -90 toward -X. The sun rises at the `sunrise` of `world.daynight` toward azimuth -90 and sets at its `sunset` toward +90. ## Services, events, variables - `world.sky` (service): `body(id)` returns `{ direction, azimuth, elevation, up, phase, illuminated, visible, light, color, angularSize, glow }`; `visible()` the ids that show; `list()` every body as defined with its look; `set(id, patch)` changes a body during the game and is saved (`null` puts it back); `sun()`, `stars()` (rotation and pole of the star field), `visibility()`. - `sky.rise`, `sky.set` (events): a body crossed the horizon, with `id`, `azimuth`, `day` and `hour`; once per crossing, never at the start nor after a load. - `sky.phase` (event): a body lit by the sun became `full` or `new`. - Variables, written at the game tick only when they change: per body `azimuth`, `elevation`, `phase`, `illuminated` and `visible` under `sky.` and its id; `sky.nightLight`, `sky.visibility`, `sky.stars.rotation`, `sky.stars.visible`, `sky.sunAzimuth` and `sky.sunElevation` (where the sun is, for the phases), and `sky.revision`, which moves when a definition or a look changes (read `list()` again then). - Night lights: each body with a `nightLight` lights the ground from where it stands, as bright as its lit fraction, its visibility and its height let it, only at night, in a desaturated colour of the body. The brightest `nightLights.max` are `sky.light..azimuth`, `.elevation`, `.intensity`, `.color` (`0xRRGGBB`) and `.shadows` (n from 0, the brightest first, which alone casts the shadows), drawn by the presentation module as directional lights; `sky.nightLight` is their total. With `world.daynight` 1.2 its stand-in night sun steps down to a faint minimum (`nightSun: "auto"`). ## Hooks, verbs and modding The hook `sky.body` is asked with `{ id, kind, reason, color, angularSize, glow, visibility }` at the start, after a load, a config change or a `set`, and each time the body rises. `color`, `angularSize`, `glow` and `visibility` (a factor from 0 to 1) are mutable; it is not cancellable. ```json { "id": "blood_moon", "hook": "sky.body", "match": { "id": "moon", "reason": "rise" }, "set": { "color": "#b0302a" } } ``` Scripts use the action `set_body` (`{ "type": "set_body", "body": "moon", "hidden": false }`), the conditions `body_up` and `body_visible` (with `min`), and missions the step `wait_rise` (`{ "type": "wait_rise", "body": "moon" }`, done when the body is up; `next: true` waits for the next rise). Every setting is moddable and live. ## When it does not do what you expect - A body never shows: it is `hidden`, below the horizon, or the visibility phases give 0 at this driver value. - The sky does not move: `world.daynight` is not installed, or its clock is paused. - A body never rises: at a high `latitude` a body far from the equator stays up or down all day. - No `sky.phase`: the body has `lit: false`. ## Limits Orbits are simple: no eccentricity, no seasons (the sun stays on the celestial equator), no eclipses yet. Its presentation module draws each body as a sphere with its phase, procedural surface, halo and rings, and the star field; it does not draw a `textureSlot` yet (the procedural surface shows instead). Bodies and stars are added over the sky: the unlit side of a body shows the sky behind it, and a lit sky washes the stars out. ## Versions Kit `world.sky_bodies` 1.0.0, requires `game.engine@^1`; uses `world.clock` of `world.daynight` when installed.