Developers Manual
Getting the source
Kosmos is built on simlib, which is a separate library. Check both out and install simlib once, then build Kosmos against the installed copy.
git clone <kosmos repository> kosmos
git clone <simlib repository> simlib
A fresh checkout runs as it is: it carries a default texture set (see below) and procedural terrain. The optional data described in this manual only improves detail.
Building
| Command | Result |
|---|---|
scripts/build-linux.sh | Clean build of build/package/kosmos-beta-Linux.tar.gz. Needs simlib installed under SIMLIB_PREFIX (default ~/opt). |
scripts/build-windows.sh | Cross-build of build-win/package/kosmos-beta-Windows.zip (mingw-w64 and vcpkg; simlib under ~/opt-win). |
INCREMENTAL=1 scripts/build-linux.sh | Reuses the existing build tree instead of cleaning first. |
KOSMOS_SIMLIB_DIR=/path/to/simlib | Builds against a simlib source tree instead of an installed one. |
For everyday work configure once and use Ninja:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$HOME/opt
ninja -C build
build/kosmos --check-data
--check-data loads, saves and reloads every scenario and base and reports any that do not round-trip. Run it after editing data. See Testing & screenshots for the screenshot options.
Repository layout
| Path | Contents |
|---|---|
src/ | The simulator. |
addons/ | Ships, aircraft and MFD modes. |
data/ | Bodies (sol.json), systems, scenarios, bases, scripts and textures. |
data/celestia/ | Celestia source data. Moon sources are grouped under moons/<planet>/<Moon>/; that source-only tree is not scanned at runtime and is excluded from packages. Large optional hires/ packs are git-ignored and excluded from packages. |
data/parts/ | Shared part kits (a folder with part.json and a glTF mesh) for building multistage rockets; see Part kits. |
data/blender/ | kosmos_edit.py, the script Blender runs for “Edit in Blender” in the part editor and the base editor. |
data/textures/ | Installed runtime fallback images and normal maps, in the repository. Import desired source maps here with tools/celestia_import.py. |
data/textures-hires/ | Optional installed image and normal-map overrides. Git-ignored, never packaged. |
data/orbiter/ | Optional Orbiter terrain and imagery archives, read directly at run time. Git-ignored, never packaged. |
data/bodies/, data/nasa/ | bodies/ holds glTF shapes that bodies use through "mesh". nasa/ is the folder of NASA 3D Resources downloads that tools/nasa_import.py converts into data/parts/nasa_* and bodies/vesta.glb (needs Blender and pip install DracoPy trimesh). nasa/ is Git-ignored and never packaged. |
tools/ | Import and generator scripts (Python). |
scripts/ | Build, release and publish scripts. |
Ship systems architecture
Ship power, consumables and cabin air live in two layers: a general-purpose gas grid in simlib, and the Kosmos model that uses it. The data formats are in the Modders Manual; this section is how the code fits together.
Files
| Path | Contents |
|---|---|
rsimlib/include/gas_vessel.h, src/engine/gas_vessel.cpp | sl::GasVessel: a 12³ cellular grid of three gases (O2, N2, CO2). Exported through sl.h. Test: gas_vessel_test in simlib. |
src/systems.h, src/systems.cpp | SystemComponent, VesselSpec, parse_systems, the Systems runtime class, find_tagged_vessels and LifeSupportMode. |
src/ship.cpp, src/ship.h | ShipSpec::systems, Ship::systems_, the per-step hook, staging and property publishing. |
src/part_kit.cpp | Forwards a kit’s systems and vessels into the ship with the stage index set. |
src/scenario.cpp, src/sim.cpp | The life_support and per-ship systems scenario keys; Sim::life_support is the default applied in Sim::spawn. |
src/kosmos.cpp, src/menu.cpp, src/mfd.cpp | The F1 window, HUD alarm, scenario-editor fields, and the bus-power gate on MFDs and lights. |
addons/stockmfd/systems.lua | The Systems MFD mode. |
tests/systems_test.cpp | Unit tests (see below). |
The gas grid (simlib)
FluidSimulation3D has no non-buoyant gas and its smoke and steam expire, so cabin air is a separate, simpler type rather than a mode of the fluid solver. The grid is a cube whose outer layer is the hull. Each step exchanges a fraction of the density difference between neighbouring open cells, which conserves mass; it is stable up to 1/6 per face and the step is split into sub-steps. Vent cells sit in the hull layer and drain their neighbours at a given rate to vacuum, and gas_vessel_step returns the mass vented. The API is plain functions: create_gas_vessel, gas_vessel_fill, _inject, _consume, _set_vent, _step, _total, _cell, _open_cells and _set_total. After changing simlib, rebuild and install it (cmake --build build --target simlib, then cmake --install build) before building Kosmos.
Authority: lumped masses
Each Systems::Vessel keeps lumped masses o2, n2 and co2 in kilograms. These are the truth: pressure, partial pressures, hazards and leaks are computed from them (ideal gas, fixed temperature). The grid only distributes the gas for display and local effects. Each step crew inject CO2 and consume O2 at their cells, the grid mixes, and then gas_vessel_set_total rescales each gas to its lumped total, so the grid can never drift from the books. Above SystemsContext::grid_dt_limit (30 s) the grid skips diffusion and only rescales, which keeps high time warp cheap.
A leak is a decay constant k = aream² × 170 / volume per second, applied as exp(−k·dt) to all three gases, so it is exact at any step size.
The step
Ship::after_step works out the Sun’s direction in the ship frame, the flux relative to 1 AU, and whether a body shadows the ship (a ray–sphere test against every non-star body), and passes them in a SystemsContext to Systems::step(dt, ctx). Order inside a step:
solve_busin 10 s substeps (at most 2000 per call, so warp works). Fixed sources (solar, RTG) come first. Loads are then walked by priority, all-or-nothing; a load that does not fit is shed. The shortfall is met by the dispatchable sources (fission, fusion, fuel cell) and then by the batteries, and any surplus charges the batteries. Electrolyser output is applied after the bus.step_vessels: crew metabolism, grid sources and sinks, scrubbers and regulators (only when powered), leaks, the grid step and total sync, then hazard classification and the health clock.- Alarms are collected and, in
fatalmode, a vessel whose crew health reaches zero setscrew_lost_;Shipthen setscrashed_and the HUD says CREW LOST.
Hazard thresholds, crew rates and conversion constants are the constexpr values at the top of systems.cpp and in step_vessels. Health only changes when the mode is not off, and only for vessels with crew.
Stages
Components and vessels carry a stage index (names are resolved against the ship’s stage names at parse time; the default is the last stage). Ship::drop_stage calls Systems::drop_stage, which marks the equipment of that stage dead and removes its vessels. Stages drop bottom-first, so equipment in the top stage survives.
Tagged meshes
find_tagged_vessels runs at the end of load_ship_spec and needs no graphics context. It reads each part model’s file: OBJ files are parsed directly (the loader makes one part per usemtl group, so that name is the tag), and other formats go through sl::load_model. It takes the bounds of every object named vessel*, vent* or crew*, transforms them by the part’s placement, and merges the result with a same-named JSON vessel. Visuals::part_model in kosmos.cpp hides those objects when drawing. Note that spec.directory must be set (it is derived from the file path when empty) before the models can be found.
Properties, UI and power gating
Systems::publish ties getters under /ships/ship[N]/systems/, including the write-only vessel[i]/breach-cm2. The F1 window and HUD alarm read the Systems object directly; the MFD reads properties. Systems::instruments_powered() is false only when the bus has no electricity at all; MfdSystem::bus_power is set from it each frame (MFDs act as switched off) and ship lights skip drawing. Cabin gas heat-maps come from Systems::grid_cell.
Tests
tests/systems_test.cpp is excluded from the normal build. Run cmake --build build --target systems_test && build/systems_test. It covers parsing and stage defaults, solar and eclipse charging, load shedding, scrubber and regulator behaviour, a leak, the fatal path, stage drops, the electrolyser, and a tagged OBJ model written to a temporary directory. Tests must #undef NDEBUG before including <cassert> or the asserts compile away in release builds. simlib’s own gas_vessel_test covers mass conservation, venting and set_total.
Limits and ideas
- One temperature, ideal gas, no heat or radiation. No fire or toxic gases.
- Propellant tanks are separate and unchanged. There is no plumbing between tanks.
- Hatches and docking do not connect vessels yet; each vessel is sealed from the others.
- Graphics paths other than OpenGL (Vulkan, D3D12) are unaffected by this feature but untested with it.
Default & hi-res textures
Keep source collections separate from the images Kosmos loads as runtime textures. Surface images are equirectangular maps named after the body in lower case:
data/celestia/anddata/orbiter/hold source collections, not general runtime image folders. Celestia moon sources are organized by parent planet undermoons/<planet>/<Moon>/; Kosmos does not scan that tree at runtime, and it is excluded from release packages. Large Celestia packs underhires/and Orbiter data are also excluded.data/textures/: the installed default set (Earth 5400×2700, Moon and Mars 2048×1024, the other planets and moons 1–4k), including normal maps. It is in the repository, so a plaingit cloneworks.data/textures-hires/: optional installed replacements and normal maps, usually up to 16384×8192. It is git-ignored and excluded from packages.
| File | Used for | Default (in the repo) |
|---|---|---|
earth.dds | Earth from orbit | textures/earth.jpg |
moon.dds | The Moon from orbit | textures/moon.jpg |
mars.dds | Mars from orbit | textures/mars.jpg |
earth_lights.dds | Earth’s city lights on the night side | textures/earth_lights.jpg |
earth_clouds.dds | Earth’s cloud layer | a procedural cloud layer |
<body>.dds / .png / .jpg | Any other body | textures/<body>.jpg |
Above about 300 km for Earth (scaled by body radius for other worlds), the whole-body image or streamed Earth tiles are drawn. Closer in, Orbiter Elev.tree continues to provide terrain heights when installed; the terrain chooses the finer available colour imagery per location from Orbiter Surf.tree and the Celestia map or tiles. If neither provides imagery, it uses procedural terrain colours.
How textures are found
For each name the game checks data/textures-hires/ first, then the optional matching Celestia hi-res folder described below, then data/textures/. Within a folder the extensions are tried in the order .dds, .png, .jpg, .jpeg. So to override a body, drop a file of the same name in data/textures-hires/; to go back to the default, delete it. A body with "texture" set in sol.json uses exactly that path under data/ instead. Close-up terrain independently compares available Orbiter and Celestia colour imagery at each location and samples the finer source; normal maps (<body>_normal.jpg or .png) are looked up in data/textures-hires/ or data/textures/, with Celestia normal-map folders also supported as described below.
DDS files (BC1, BC3 or BC7, with a mip chain) are uploaded to the GPU still compressed: a 16384×8192 BC1 Earth costs about 90 MB of video memory and almost no system memory. PNG and JPEG are decoded first and cost about 1.3 GB of RAM at that size. Use --max-texture N to cap the size if your GPU is short of memory.
Getting and installing Orbiter hi-res data
Kosmos can use the planet packs from the Orbiter space flight simulator in two ways. Neither is included in the repository or in the downloads.
- Get the packs. Download the Orbiter 2016 high-resolution texture packs for Earth, Moon and Mars (from the Orbiter site or the Orbiter-Forum add-on list). Each pack holds
Elev.tree(heights) andSurf.tree(imagery); Earth also has cloud and mask layers. Read each pack’s licence first. - Terrain and close-up imagery (read directly). Unpack each pack as its own folder inside
data/orbiter/. The folder name must end with the body’s name:
Nothing else is needed. The console printsdata/orbiter/ Orbiter 2016 High Res: Earth/ Elev.tree Surf.tree ... Orbiter 2016 High Res: Moon/ Elev.tree Surf.tree Orbiter 2016 High Res: Mars/ Elev.tree Surf.treeEarth terrain from Orbiter data(and so on) at start-up for each body it found. - Whole-planet maps for the orbital view (converted once). Convert each
Surf.treeto a single compressed DDS indata/textures-hires/. The tool copies the compressed tiles block for block (no decode), so even a 16384×8192 map converts without decoding anything:
The result ispython3 tools/orbiter_import.py "data/orbiter/Orbiter 2016 High Res: Earth/Surf.tree" --body earth --level 8 python3 tools/orbiter_import.py "data/orbiter/Orbiter 2016 High Res: Moon/Surf.tree" --body moon --level 8 python3 tools/orbiter_import.py "data/orbiter/Orbiter 2016 High Res: Mars/Surf.tree" --body mars --level 8data/textures-hires/earth.dds,moon.ddsandmars.dds. The first argument may also be an Orbiter installation folder together with--body.--level 8gives 16384×8192;--level 7gives 8192×4096.--out DIRchanges the output folder.python3 tools/orbiter_import.py --selftestchecks the tool. - Check it. Start Kosmos and look for
surface texture .../textures-hires/earth.dds: 16384x8192in the console, or compare an Earth orbit shot before and after.
The importer reads only the Surf.tree layer. Earth’s night-light and cloud maps (earth_lights.dds and earth_clouds.dds) are optional and are not produced by it; without them the game uses the default city-lights image and the procedural clouds.
You can install only some bodies. A body without a pack, or without a textures-hires file, simply keeps its default.
Other hi-res sources
- Your own images. Drop
<body>.png,.jpgor.ddsintodata/textures-hires/. Maps must be equirectangular (2:1), with north at the top and longitude 180°W at the left edge. - Celestia. Keep the original Celestia source tree in
data/celestia/; keep Orbiter source archives indata/orbiter/. Install runtime images todata/textures/(the bundled low-resolution defaults) or optional overrides todata/textures-hires/.python3 tools/celestia_import.pycopies the best resolution of each supported Celestia moon and planet image fromdata/celestia/intodata/textures/; use--out data/textures-hiresfor optional high-resolution single-image overrides. Large source packs are ignored and are not shipped in the repository or release. - Organizing moons. Store moon source data under
data/celestia/moons/<planet>/<Moon>/, grouped by the planet it orbits, rather than mixing moon files into the planet texture collection. Preserve the original moon name and keep related.ctxdescriptors,levelNtile folders, normal maps, and models together. For example, Callisto belongs underdata/celestia/moons/jupiter/, withCallisto.ctxbeside theCallisto/tile folder. This is a source-data organization convention; Kosmos does not scanmoons/at runtime. Install a tiled map withpython3 tools/ctx_import.py "data/celestia/moons/jupiter/Callisto.ctx" callisto --out data/textures-hires. - Celestia Motherlode. The Celestia Motherlode is a large collection of user-made Celestia add-ons, including many high-resolution planet and moon textures (Earth maps run to 64k and beyond). Download the texture add-on you want, take its colour map, and save it as
data/textures-hires/<body>.jpg(or.png, or convert it to.dds). Each add-on has its own licence, and many are Creative Commons, so read the one that comes with it before you redistribute anything. Maps larger than your GPU’s texture limit (commonly 16384 pixels) should be reduced first, for example with ImageMagick:magick earth64k.jpg -resize 16384x8192 data/textures-hires/earth.jpg. - Tiled Celestia maps (64k Earth and similar). The very large maps are split into tiles: a
name.ctxdescriptor beside a folder oflevel0,level1… folders holdingtx_<column>_<row>.ddsfiles. Each level doubles the size in both directions; for the “JMII DDS” Earth, level 3 is 16384×8192 and level 5 is 65536×32768. Earth streams its tiles directly fromdata/celestia/hires/earth/. For another body,python3 tools/ctx_import.py "data/celestia/hires/path/to/name.ctx" bodystitches the deepest level that fits in--max-width(default 16384) intodata/textures-hires/body.jpg.--listshows the levels and--level Nforces one. Remove or rename an existingbody.ddsin the output folder first, because.ddsis looked for before.jpg. - Streamed Earth (tiles, specular and normal map). If the Celestia Earth folders are present, Kosmos draws Earth from the tiles at run time instead of a single image, so you get the full 64k level 5 map plus the sparse deeper levels 6–10 (probably UK only) where they exist; elsewhere it falls back to the coarser tile. Only the tiles in view are loaded. Put the three source packs under
data/celestia/hires/(large source packs are ignored):earth/– the colour map, for example the “JMII DDS” pack: a.ctxfile beside itslevel0…level10folders. DDS tiles in DXT1, DXT3 (BC2), DXT5 and BC7 are all supported.earth_specular/(earthspec32k.ctx) – an optional gloss map that adds sun glint on the oceans.earth_normal/(the 32k “earth-normals-hs-32k” pack, JPG tiles intextures/medres/earth-normals-hs-32k/) – an optional normal map that gives the terrain relief. It is applied only where a normal tile exists at the same level as the colour tile being drawn (levels 0–5), so very close views of the sparse deeper levels have no relief. The relief is subtle from orbit and is switched off by--no-relief.
earth/folder Earth uses the single image as before, and--no-tilesforces that. The other bodies still use one image each (their normal maps are<body>_normal.jpg, as above). Seams between levels are hidden by skirts rather than stitched, and the streamed Earth replaces the sphere only above 300 km, where the procedural terrain takes over. - Other Celestia hi-res maps and normals. Optional files in
data/celestia/hires/<body>/are used after explicit overrides indata/textures-hires/and before the bundled fallback indata/textures/. For example,jupiter/jupiter-hubble-2015.jpgis used for Jupiter. A normal-map image indata/celestia/hires/<body>_normal/is used automatically;mars_normal/MarsNormal.pngis used for Mars. Large optional source packs underdata/celestia/hires/are git-ignored and excluded from packages, so a clean checkout uses the bundled maps. The Jupiterjupiter-hubble-2015-bump.jpgis a grayscale height/bump map, not a tangent-space normal map, and is not loaded as a normal. - Test maps.
python3 tools/gen_textures.py [--scale N] [body ...]generates procedural BC1 DDS maps (default outputdata/textures-hires/);--scale 4makes them a quarter size for a quick test. - Moon normal maps.
python3 tools/gen_moon_normals.pywrites<body>_normal.jpgrelief maps for airless moons from their colour textures.
Licences
The repository, and every download, may contain only data we are free to redistribute. data/orbiter/, data/celestia/hires/ and data/textures-hires/ are git-ignored and excluded from packages. data/celestia/moons/ is also excluded from packages because it is source-only, though selected source files may be kept in the repository. Check the licence of any map before redistributing it or installing it in data/textures/.
Packaging & release
scripts/release.sh builds the Linux and Windows packages and publishes them to the web site (--no-publish, --linux-only and --windows-only narrow it). Publishing only acts when an archive’s checksum changed; it then stamps the new version on the pages and moves VERSION on to the next build. The packages contain addons/ and data/ except for data/orbiter/, data/celestia/hires/, data/celestia/moons/ and data/textures-hires/, plus the monospace font. A download therefore uses the bundled runtime fallbacks until its owner adds optional hi-res data as described above.
Roadmap
Ideas under consideration. Nothing here is scheduled or implemented; they are recorded so the design can be reviewed first.
Native addon modules (C interface)
Addons today are data plus sandboxed Lua. This item would let an addon also ship compiled code, as Orbiter add-ons do, through a strictly C ABI so modules can be written in C, C++, Rust or Zig and Kosmos never exposes its C++ types. The full design is in docs/native_addon_roadmap.md in the source tree.
- Interface: one pure-C99 versioned header,
kosmos_module.h. Structs start with a size field so they can grow, handles are opaque, and the host passes a function table tokosmos_module_init, so a module never links against the executable (this also suits Windows). - Module kinds: global plugins, vessel modules (a ship in
addon.jsonnames a module and class; the module gets create, destroy, pre/post step, events and panel drawing) and MFD mode modules. - Host API: the property tree, universe and ship state, controls, forces, spawning and staging, events, a 2D canvas (the MFD command list), input and audio.
- Rendering: simlib is C++ with no C API, so modules would get a curated C wrapper: load meshes and textures, queue
draw_meshcalls, add lights and particles, and hook before and after the world draw and the screen overlay. The host keeps the GL, Vulkan or D3D state. - Safety: off by default, enabled by a Settings checkbox with a warning. Native code cannot be sandboxed, so the protections are the opt-in, a logged module list, an API version check and error isolation at the C boundary. Callbacks run on the main thread. Allowed in multiplayer, where a module that changes physics may desync players.
- Phases: header and loader; property and vessel API; vessel module lifecycle; 2D, MFD and HUD hooks; 3D draw hooks (the biggest unknown); then examples, tests, docs and Windows packaging.
- Main risks: keeping the ABI stable, a faulty module crashing the game, floating-origin frames for applied forces, and the rendering wrapper growing into a copy of simlib.