Shamash
This aims to be a physically accurate worldbuilder. A lot of these calculations are rather educated guesses than well-founded scientific principals, but they all have their foundation in recent scientific discoveries and laws of nature.
Based on the great Artifexian's Work "The Worldsmith" I wanted to continue his journey. Intruiged by building on the very idea of quantifying reality - but at the same time horrified by looking at a spreadsheet - the only logical next step was to build a python application. :)
So, before the whole thing became a webfrontend with an API I designed it to be a CLI-Application. I like to work from the terminal, but after having hundreds of parameters scrolling past your tired eyes this was just not feasable any more. So I turned the CLI into an API (which still can be used as CLI as well) and created a React-Frontend.
Worldbuilding is an incredibly complex process. This application will never aim at calculating EVERYTHING, you know. I know some of you (including me) have those thoughts and it is only natural to desire a feeling of absolute control over reality. Understanding, caluclating, quantifiying everything until its smallest detail. But look into yourself. The motivation will most likely be fear, or some derivation of it. You are afraid to not be able to hold the world you are building in the palm of your hand.
But the very thing you are seeking to do - describing a reality by natural laws - is something we humans perfected since 300.000 years by immitating our natural surroundings (impressions), using them to express our feelings, pain and pleasure (expressions). This enables you to describe your newly formed reality in a way far more compley than any table of figures can. Put your world into writing, not into a table, so other humans can comprehend what you are creating.
The shamash repositories
shamash is split by audience:
- shamash-core (this repo) — the physics engine and the
shamashCLI. Published on PyPI. - shamash-server — the HTTP API around the engine, dockerized.
- shamash-web — the React frontend, dockerized, talking to a shamash-server.
Getting started
pip install shamash-core
shamash -p
The distribution is shamash-core; the command and the import name are both shamash.
For development, a Makefile is provided:
make venv
uv pip install --python venv/bin/python -e ../naturedynamics # the physics backend, as a sibling checkout
make install
make run
make test
Without any paramaters, shamash reads the seed world packaged at src/shamash/data/default.yml, which represents our very solar system. make run is configured to use the -p flag, which pretty-prints out all calculations.
The CLI application
shamash is the terminal-first way to use this project — load a world, optionally change what gets read or written, and exit. A plain shamash prints nothing; it just loads the world and returns, which is what makes it safe to script. Every flag is optional and they compose:
shamash # load src/shamash/data/default.yml, print nothing
shamash -w my_world.yml # load a different world file instead
shamash -p # also pretty-print the loaded (and computed) world to stdout
shamash -e out.yml # also export the loaded world to out.yml
shamash -f json # write JSON instead of YAML (also: csv)
shamash -i new_world.yml # copy the input world to new_world.yml, to seed a new one, then exit
shamash -l # also log to the configured log file (stderr always gets the log otherwise)
-p and -e render through the exact same writer, so shamash -p > world.yml produces a file that reloads with -w byte-for-byte. That holds per format: -f json -e world.json reloads with -w too, and gives back the identical world. On a terminal, -p syntax-highlights the output with rich; piped or redirected, it falls back to the plain string, so the redirect stays clean.
-f csv is the flat one, and the exception: one row per scalar rather than a tree, entity,type,property,source,value,unit,dimension, with compound values exploded into rows of their own (main_star_apparent_size[0], atm_composition.o_2). It is the same view shamash-web's matrix tab shows, and it is one-way — a world cannot be read back from it. Use it to pull a computed world into a spreadsheet or a plotting script:
shamash -f csv -p | awk -F, '$3 == "habitability"'
Verbs
Flags act on a world; verbs change one. create makes one entity and hangs it off an existing one:
shamash create Atmosphere earth -w my.yml # asks for what it needs
shamash create Planet sol -w my.yml # offers sol's free orbits, or makes one
shamash create Star --id alpha --set mass=1.0:MSol --set current_age=4.6:Gyr -w my.yml
Flags work on either side of the verb, so shamash -w my.yml create Orbit sol reads the same.
Required fields it isn't given, it asks for; --set key=value answers one up front. --set also reaches the optional fields it never asks about — a planet's core (--set core_density=10900), a star's wind — and refuses a name the type doesn't have, so a typo is a visible error rather than a value that quietly never arrives. Values are SI unless you give a unit — --set mass=1.0:MSol goes through the same conversion and the same checks a world file's {value: 1.0, unit: MSol} does, so a wrong dimension is refused rather than quietly scaled.
Choices with no safe default are asked too: a body that already has an atmosphere, a parent that already has orbits, a file about to be overwritten. --answer name=value settles those up front (--answer strategy=add), which is what makes the whole thing scriptable — including questions about an entity create makes along the way, like the orbit a new planet needs (--answer semi_major_axis=1.52:ASol). With nothing to ask and no terminal to ask at, it refuses instead of hanging — a piped run tells you exactly which answers it was missing and exits 1.
What can host what is not a list anywhere; each relationship declares what the child reads off its parent, and a type qualifies by providing it:
$ shamash create Atmosphere sol -w my.yml
Atmosphere cannot hang off 'sol': a Star has no surface_temperature,
which Atmosphere.parent_moon_or_planet needs. Types that do: Planet.
That sentence is assembled from the models every time, so it follows them — the day a Moon can compute a surface_temperature, moon atmospheres start working and this refusal stops being produced, with nothing to update.
create writes back to the file -w names, or to -e when given. It refuses without one of them (the default world lives inside the installed package), asks before overwriting unless -y, and refuses -f csv, which does not reload.
crs is the read-only one: it prints a coordinate reference system for a body's surface, so a world can be mapped in QGIS, GDAL or anything else built on PROJ:
shamash crs earth > earth.wkt # latitude/longitude
shamash crs earth --projection north-polar # or equirectangular, south-polar,
shamash crs earth_moon_1 --projection mollweide # sinusoidal, transverse-mercator
shamash crs earth --projection equirectangular --central-meridian 180
It writes WKT2 for a sphere of the body's radius, shaped like the IAU's own planetary CRSs (which PROJ ships as IAU_2015:*): planetocentric latitude, east-positive longitude, a reference meridian at 0. Anything with a radius qualifies — a Star, Planet or Moon; an Atmosphere or Orbit is refused, naming the types that do.
One catch that is PROJ's, not ours: PROJ decides which celestial body a CRS belongs to from its radius alone, and treats any sphere of roughly 6,330–6,423 km as Earth. A fictional world that close to Earth's size will transform to WGS 84 without complaint; anything further off is refused, as it should be.
map starts and maintains a map project: a directory with an equirectangular Inkscape template for a body and one for each of its moons, ready to paint on.
shamash map init earth earth-map -w my.yml # earth-map/earth/map.svg, earth-map/earth_moon_1/map.svg, earth-map/map.yml
shamash map init earth earth-map --width 16000 --moon-width 4096 --major 30 --minor 10
shamash map grid earth-map # redraw the grids, e.g. after changing axial_tilt
Each template is width × width/2 pixels (10000 by default, 4096 for moons), longitude −180 to 180 left to right, north up. Besides the grid it has four hidden channel layers to paint in — height (grey, black −11200 m to white +9200 m by default, set per body in map.yml), water, night (city lights) and clouds — which shamash creates and never touches again (shamash map layers DIR adds them to older projects). shamash draws a locked layer, shamash: grid, with a minor and a major graticule and — for a planet with an axial_tilt — its tropics and polar circles; the unlocked surface layer, and any you add, are yours. map grid reloads the world map.yml names and replaces only shamash's layers, keeping their place in the stack and whether you hid them; it copies each SVG to map.svg.bak first. Don't run it while the file has unsaved changes in Inkscape: Inkscape does not notice the file changed and will save over it.
./earth-map/export.sh runs Inkscape once per file and writes, beside each SVG, map.png (everything you painted except the grid and the channels) and one PNG per channel — height as 16-bit grey, unpainted ground at sea level. Those PNGs, not the SVG, are what QGIS and Blender read.
shamash map georef earth-map # map.pgw + map.png.aux.xml beside every exported map.png
shamash map qgis earth-map # earth-map/qgis/project.json + build_project.py
QT_QPA_PLATFORM=offscreen python3 earth-map/qgis/build_project.py # with QGIS's Python
georef georeferences the colour map and every channel, and gives the height map the scale that turns its grey into metres, so QGIS reads it as a DEM. It reads each PNG's size from its header, so an export at any resolution lands in the right place; it warns about bodies not exported yet. qgis writes data and a PyQGIS script — shamash never imports QGIS — and the script writes qgis/<id>.qgz per exported body: its map as a basemap on the body's equirectangular CRS. It also saves every projection crs knows for the body as a user-defined CRS in your QGIS profile, so the canvas can be switched to a polar view from the CRS picker. It refuses to overwrite an existing .qgz without --overwrite. From inside QGIS, run it from the Python console with exec(open(path).read(), {"__file__": path}).
shamash map blender earth-map # earth-map/blender/scene.json + build_scene.py
blender -b --python-exit-code 1 -P earth-map/blender/build_scene.py # writes scene.blend
blender sets up the planet, its moons and its star's light at the planet's vernal equinox, at true scale (one Blender unit is the planet's radius): each body wears its exported map.png over a grey of its albedo, with its channels as bump relief, glossy oceans, city lights on the night side and a cloud layer 10 km up, the sun lamp has the star's colour, brightness and angular size, exposure adapts to the star the way an eye would, and an atmosphere glows at the limb, 12 scale heights deep and tinted with its sky colour. It is animated over one orbit of the outermost moon in 240 frames at 24 fps — the Moon on its Kepler orbit, the planet spinning at its real rate, the star's direction and brightness following the planet along its own orbit — with a close camera and a wide one; Eevee renders it. Keep --python-exit-code 1: without it Blender reports success even when the script refuses.
As a library
Everything the CLI does is reachable from Python. A world loads into a registry, every value on it is computed on access, and Worldloader turns it back into text:
import shamash as sh
with sh.god.scope():
loader = sh.services.Worldloader()
loader.load(sh.services.BaseService.default_world_path())
print(sh.god.summons("earth").habitability)
print(loader.dumps(fmt="json"))
god.scope() gives the world a registry of its own, so two worlds can be computed at once without seeing each other — that is what shamash-server does per request.
sh.utils.describe_entities() is the per-type schema — every field and computed property with its unit, whether it is required, and its description — derived from the models rather than written down anywhere.
Property documentation
Every field and computed property carries a description, and each has a long-form page with formulas and sources under src/shamash/docs/properties/<Type>/<name>.md. The pages ship inside the package, so a server always hands out the pages for exactly the engine version it runs; shamash-web renders them. make test fails if a description has no page or a page documents nothing.
Releases
Development happens on origin (dev.fs.or.at). A release is an annotated
tag on main, and codeberg only ever sees tagged states:
git push origin main # every day
git tag -a v2026.1.1 -m "CLI verbs and output formats"
git push origin --tags # keep the markers on dev too
git push codeberg main v2026.1.1 # publish
Tags go to both remotes; commits go to origin until they are released.
Then the same commit goes to PyPI:
make publish-test # TestPyPI, with $PYPI_TEST_TOKEN; check it installs
make publish # PyPI, with $PYPI_TOKEN
That way the release history survives on the remote that has everything, rather than only on the one it is published to.
So git tag is the list of releases, each heading below names one, and the
version in pyproject.toml is bumped in the commit the tag points at.
__version__.py reads it from there — see the note in CLAUDE.md about why
the checkout's pyproject.toml wins over importlib.metadata.
2026.4.0
Maps: everything from a paintable template to an animated Blender scene.
-
services/terrestrial/is nowservices/cartography/:TerrestrialServicebecameCartographyService,sh.services.terrestrialbecamesh.services.cartography. No alias is kept;crsoutput is byte-identical. shamash-server needs the same rename when it moves its pin. -
sh.services.cartographyalways names the module's exports. It used to come back as the bare subpackage once anything had touchedCartographyServicefirst (a latent bug since 2026.3.0, when it wasterrestrial). -
Planet.axial_tilt(optional), and thetropic_latitudeandpolar_circle_latitudeit gives;Orbit.raanis an optional field (it was aNotImplementedErrorstub);Atmosphere.scale_heightis computed -
shamash map init|grid: a map project directory with an equirectangular Inkscape template per body, whose locked grid layer shamash owns and redraws while leaving every other layer alone - Channel layers (height, water, night, clouds),
shamash map layers, and an emittedexport.shthat exports the colour map without the grid and one PNG per channel through Inkscape; height in metres, 16-bit, sea level snapped to a grey step -
shamash map georef: world file +.aux.xmlfor every exported PNG, sized from the PNG itself, height with Scale/Offset for GDAL/QGIS -
shamash map qgis: the data and a PyQGIS script that build one QGIS project per body on its own CRS, registering every projection as a user CRS -
shamash map blender: the planet system at its vernal equinox, true scale, physically lit and eye-adapted, with atmosphere, bump, ocean glint, night lights and clouds, animated over one orbit of the outermost moon in 240 frames keyed from Kepler's equation - Requires naturedynamics 1.2 (
astrophysics/orbital.py,scale_height,srgb_to_linear)
2026.3.1
-
crsargument mistakes are typed: an unknown projection, or a polar projection given a central meridian, raisesInvalidProjectionErrorinstead of a bareValueError, so shamash-server can answer them with a 422 rather than a 500. The CLI behaves as before.
2026.3.0
- A terrestrial service and the
crsverbservices/terrestrial/is the home for anything on a body's surface. First in: WKT2 coordinate reference systems for any entity with a radius — geographic, equirectangular, polar stereographic, sinusoidal, Mollweide and transverse Mercator — mirroring the IAU 2015 definitions PROJ ships.- A sphere for now: a rotational ellipsoid needs a flattening model, and the simple homogeneous one gets Earth wrong (1/232 against 1/298).
- Tested against PROJ itself through pyproj (a dev-only dependency): every CRS parses, and known points land where the closed-form projection says they must.
2026.2.0
- Split the project into repositories
- This repo becomes shamash-core: the engine and the CLI, published
on PyPI as
shamash-core. The import name and the command stayshamash. - The HTTP layer moved to shamash-server and the frontend to
shamash-web, both dockerized; the marimo notebooks to a private
experiments repo.
shamash-apiis gone from this package. - The property docs moved into the package
(
src/shamash/docs/properties/), next to the descriptions they expand on, so a page and its description change in one commit. The coverage test moved with them and now checks both directions, the Settings pages and the sidebar — it used to check only that entity descriptions had a page, and skipped itself in any install without the frontend. describe_entities/describe_configmoved fromapi/schemas.pytosh.utils: they read only the models.- MIT licensed, in all three public repos.
- Dependencies cut to what the engine imports:
pyyaml,rich,naturedynamics.numpy/scipy/matplotlibarrive throughnaturedynamics, which declares them itself.
- This repo becomes shamash-core: the engine and the CLI, published
on PyPI as
2026.1.2
- Bring back a test suite —
make test, stdlibunittest, ~1 s- It loads the real seed world and asserts what comes out, rather than
mocking collaborators: the physics is a pure function of
default.yml, so running it is the test. Most of it is a transcription of numbersCLAUDE.mdalready presented as regression baselines — Sol at 1.000 RSol / 5770.8 K / G2.9, Earth habitability 1.000,#B3F49A, magnetopause 9.52 R⊕ — which described a suite nobody had written down. - 70 tests over the physics anchors, the loader's round-trips and error
taxonomy,
god.scope()isolation under 8 threads, the HTTP contracts (since moved to shamash-server's suite), the CLI, and thecreateverb's parent rules. - Mutation-probed while being written, which is the only reason to trust
it: feeding
Star.radiuskilograms fails 6 tests, droppingFACTOR_FLOORfails the hostile-world test, removing thePlanet.moonsbackref fails the tidal one. make test-packagingis separate (~15 s): it builds a wheel and runs the CLI and the API from it with no source tree in sight.
- It loads the real seed world and asserts what comes out, rather than
mocking collaborators: the physics is a pure function of
- Two bugs the suite found before it was finished
create Moon solran the orbit cascade before discovering a Star cannot host a moon, so it created an orbit and then refused — leaving it behind in a world the caller was told nothing had been added to. Legality is now decided first, so a refusal costs nothing.createasked its questions before validating--setnames and the--id, so a typo was reported only after you had answered three prompts. Both are checked up front now.
- Corrected numbers the docs stated but the code did not produce
magnetosphere.pydocumented Earth's magnetopause at 9.6 planetary radii and the bow shock at 12.5, in five places including user-facing text mirrored into the docs site. The code computes 9.52 and 12.37.
2026.1.1
- Output formats for the CLI —
-f yaml|json|csv- Notation moved out of both the codec and the HTTP layer into
services/world/formats.py, so there is one YAML dumper call and one JSON dumper call in the codebase and the CLI and API cannot drift.-pand-eare byte-identical per format;shamash -f json -pis byte-identical toGET /v1/world/default. - csv is the flat view — one row per scalar, the same shape shamash-web's matrix tab shows — and is one-way.
Worldloader.loadsnow parses a root-level{as JSON. PyYAML follows YAML 1.1, where5e-05without a dot is a string, so the app could not read back a file it had just written with-f json -e.
- Notation moved out of both the codec and the HTTP layer into
- CLI verbs —
shamash create <Type> <parent_id>cli.pygrows subparsers and aVERBSmap; flags work on either side of the verb.-wnames the file the verb modifies.- What can host what is declared per relationship as
parent_needs— what the child reads off its parent — never as a table of types. The refusal message is assembled from the models, so it follows them: the day a Moon can compute asurface_temperature, moon atmospheres start working with nothing to update. - Services never prompt. They call
self.ask(questions); the CLI injectsInteractionService.askand everything else getsrefuse_to_ask, which raises a 422 carrying the questions — a service reading stdin would block a uvicorn worker forever, andworld:create-orbitruns the same code. --setand--answermake a create fully scriptable; with nothing left to ask it needs no terminal at all.
2026.1.0
- Docker, and a React web frontend — both now live in their own repos, shamash-server and shamash-web, together with their design notes.
- Atmospheric
sky_color— the colour of the sky from the ground- Single-scattered starlight through the Rayleigh column, von Kries adapted
to the local star. Earth reads
#788CC4; a 90 atm column washes out to near-white, which is what a saturated column should do.
- Single-scattered starlight through the Rayleigh column, von Kries adapted
to the local star. Earth reads
2026.0.4
- Stateless HTTP API (FastAPI), so a web frontend can do what
-wdoes — now shamash-server. What it needed from the engine stayed here:- Phase 0: make the package survive outside its source checkout
- Resolve version/name/description without reading
pyproject.tomlat import - Ship
data/default.ymlas declared package data - Stop the exporter crashing on optional fields a world leaves out
- Validate a world file's units against the dimension the field declares
- Log normal conditions at DEBUG instead of ERROR
- Resolve version/name/description without reading
- Request-scoped
godregistry (contextvars) -
Worldloader.loads/load_data/to_dictseams, JSON and YAML in/out - Console entry point
shamash
- Phase 0: make the package survive outside its source checkout
v2026.0.3
- Refactor and Introduce Atmospheric model
- Maybe even in naturedynamics, taking atmospheric models from omniastro
- Introduce Plant Color and Biosphere Module
- Based on Star and Atmosphere
- Geomagnetic Dynamics/ Solarmagnetic Dynamics
- Make Habitability not true or false but a probability scale, taking into account:
- Class of the Star and Brightness
- Calculation Method relative habitability to how far the planet is away
v2026.0.2
Settled on name for project: shamash.
- Rename Project
- Refactor export/import handling
- do not use pprint, but format in cli as well
- use flag for cli dump
- Unify Units handling, refactor bad claude code
- Every function should have their units as metadata with them - decorators?
- Distances in space are AU/Parsecs/Lightyears
- Fix main_star_apparent_size=<error: division by zero>
- Improve logging
- Add a flag to write to a log file
- Star Class Calculation
- Add Star Classes as Enums, maybe directly into naturedynamics
- Fix Hillsphere Calculation and Inner/Outer Moon Zone Error
- Also push naturedynamics when pushing worldcreator
- As of 2026-09-01 naturedynamics is no longer a submodule; it lives at
~/Dev/projects/naturedynamicsand is installed into the venv. - Release Versioning and Cycle ?
- As of 2026-09-01 naturedynamics is no longer a submodule; it lives at
- Add Horizon Distance
- Add proper error handling
- Add a flag to init a new world yaml file
shamash init new_world
- Add Flag to point to yaml world file
- Make color calculation refined and add Hexcolor support
v2026.0.1
Finally, first working version finished, shamash.
Backlog
- Add Habitable Zone Shift Factor for planets, to shift habitable zone bonds,
- regarding how many greenhouse gases exist
- Axial Tilt
- Orbital Period
- Make Apparent Brightness, etc a function that can be calculated at any given point of the orbit.
- Add LSol as Unit for Luminosity
- Change ASol to AU
- Decide what log level a caller's mistake deserves
AppErrorlogs at ERROR on construction. Over HTTP every 422 is the client's bad world, not a server incident, so a busy API fills the error log with things no operator can act on — but the same exception from the CLI, against a world file you just edited, is worth an ERROR. One class, two contexts; Phase 0 already establishedlog_level=as the mechanism.
- Containerise — done under 2026.1.0 above, once unblocked on
2026-09-01 by the package no longer reading
pyproject.tomlat import, shipping its seed world as package data, and running (CLI and API both) from a wheel with no source tree. -
make venvpins--python 3.13while the venv in use is 3.14.6- Pinned to 3.14, what everything is verified on. It also gained
--seed: without pip in the venv,make installcould not run on a fresh one. And itsuv pip install -r requirements.txtnow namesvenv/bin/python: uv looks for.venvor an activated env, so it failed unlessvenvwas activated.
- Pinned to 3.14, what everything is verified on. It also gained
Metadata
Release files for shamash-core 2026.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| shamash_core-2026.4.0.tar.gz | 187.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| shamash_core-2026.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 401.2 kB
Release files / shamash_core-2026.4.0.tar.gz
| Download URL | shamash_core-2026.4.0.tar.gz |
|---|---|
| Size | 187.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7aae7fbff5387713ac6abf94ddecd945e014e35782d346097826335141829b99
|
|
BLAKE2b-256 checksum How to use checksums |
8d286e54a6bef24c3db1a79de2c26802513d2dc79dbc61a5b1f4b1eb7202995b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / shamash_core-2026.4.0-py3-none-any.whl
| Download URL | shamash_core-2026.4.0-py3-none-any.whl |
|---|---|
| Size | 214.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e8b21e1fa14cb9e6390bd94f9df2302cdf6779e307d54b12c4c748270ba58ce0
|
|
BLAKE2b-256 checksum How to use checksums |
e1fbbb19793ff3afb49b70e92f7a8ecb4d62dfdf02e59b4f88001999f3b58916
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|