A Python implementation of the star system and mainworld generation rules from the Traveller 2022 Core Rulebook and World Builder's Handbook (Mongoose Publishing), with a REST API built on Azure Functions.
All 13 mainworld generation steps are implemented in rulebook order, each feeding into the next exactly as the rules describe. The stellar and orbit generation modules implement the full WBH expanded procedure. Output is available as a plain-text summary, structured JSON, or a self-contained HTML display card.
Note: This project requires the Traveller 2022 Core Rulebook and World Builder's Handbook (Sept 2023). The Traveller game in all forms is owned by Mongoose Publishing. Copyright 1977–2025 Mongoose Publishing. All rights reserved. This project is an unofficial fan work created under the Mongoose Publishing Fair Use Policy. It is not affiliated with or endorsed by Mongoose Publishing. No commercial use is intended or permitted.
- Stellar generation (WBH pp.14–29): primary star type, subtype, mass, temperature, diameter, luminosity, system age, multiple stars (Close/Near/Far/ Companion), and non-primary star typing (Random/Lesser/Sibling/Twin)
- Orbit placement (WBH pp.36–51): world counts, MAO, HZCO, habitable zone, baseline number and orbit, spread, and mainworld candidate selection
- Mainworld generation (CRB pp.248–261): all 13 steps with temperature derived from orbital position rather than a random roll
- Complete UWP output, all 18 trade codes, all Amber zone triggers
- Verified TL era labels (Primitive → High Stellar) against pp. 6–7
- Three output formats: text summary, JSON, standalone HTML card
- SVG system maps — visual orbit diagrams with arc zones per star, log-scale AU radii, and orbit data tables
- REST API via Azure Functions (11 route handlers, JSON + HTML + plain-text responses)
- REST API via FastAPI + uvicorn — identical endpoints, no auth, per-IP rate limiting; designed to run behind a gateway
- TravellerMap integration — fetch canonical UWP + stellar data from travellermap.com and generate a full procedural system
World.from_dict()deserialiser — reconstruct a World from a previous JSON response and feed it into a new system generation- JSON Schema for the world output format (
traveller_world_schema.json) - Qt desktop UI (
gen-ui/app.py, PySide6) — cross-platform native desktop application: procedural mainworld and full system generation, TravellerMap lookup with disambiguation, in-app world card and stellar/orbit tables, attach detail (secondary world SAH + moon sub-rows), save to JSON/HTML/text, open in browser
traveller-world-gen/
│
│ Package (pip-installable)
├── pyproject.toml # Package metadata + CLI entry points
├── src/traveller_gen/ # All generation modules live here
│ │
│ │ Generation modules
│ ├── traveller_stellar_gen.py # WBH stellar generation (pp.14-29)
│ ├── traveller_orbit_gen.py # WBH orbit placement (pp.36-51)
│ ├── traveller_system_gen.py # Integration: full system + mainworld
│ ├── traveller_world_gen.py # CRB mainworld generation (pp.248-261)
│ ├── traveller_world_detail.py # Secondary world SAH/social + satellite detail
│ ├── traveller_world_physical.py # WBH physical world detail (density, gravity, tidal lock, etc.)
│ ├── traveller_belt_physical.py # WBH belt physical detail (span, composition, bulk, rating)
│ ├── traveller_hydro_detail.py # WBH hydrographic detail (surface liquid percentages)
│ ├── traveller_moon_gen.py # Significant moon sizing and profiles
│ ├── traveller_map_fetch.py # TravellerMap integration: fetch UWP + stellar, reconstruct system
│ │
│ │ Shared data
│ ├── world_codes.py # StrEnum/IntEnum types: StarportCode, TradeCode, TravelZone, etc.
│ ├── tables.py # Centralised display-layer lookup tables (single source of truth)
│ │
│ │ Visualisation
│ ├── system_map.py # SVG star system maps: arc zones, orbit tables, log-AU scale
│ ├── render_system_json.py # Standalone HTML renderer for system JSON files
│ │
│ │ HTML rendering
│ ├── html_render.py # Jinja2 rendering environment for to_html() output
│ ├── templates/
│ │ ├── world_card.html # Single-world display card
│ │ ├── world_list.html # Multi-world list (--html with multiple worlds)
│ │ ├── system_card.html # Full system card
│ │ └── system_detail.html # System card with secondary world detail
│ │
│ │ Schema
│ └── traveller_world_schema.json # JSON Schema (draft 2020-12) for World.to_dict()
│
│ Azure Functions API
├── azure-api/ # Azure Functions deployment root
│ ├── function_app.py # All HTTP endpoints (v2 model)
│ ├── shared/
│ │ └── helpers.py # Request parsing & response helpers
│ ├── host.json # Azure Functions host configuration
│ ├── requirements.txt # Python dependencies (Azure + Jinja2)
│ └── local.settings.json.example # Local development settings template
│ # (copy to local.settings.json — not committed)
│
│ FastAPI server
├── fastapi/ # FastAPI + uvicorn server (same endpoints, no auth)
│ ├── app.py # All HTTP endpoints + SlowAPI rate limiting
│ ├── helpers.py # Request parsing & response helpers
│ ├── requirements.txt # Python dependencies (FastAPI + uvicorn + slowapi)
│ └── local.settings.json.example # Local development settings template
│
├── requirements-dev.txt # Dev dependencies (pytest, pylint, pyright, httpx2)
│
│ Qt Desktop UI (PySide6)
├── gen-ui/
│ ├── app.py # Qt desktop UI — cross-platform, no system packages required
│ ├── README.md # Setup, usage, and keyboard shortcut reference
│ └── requirements.txt # PySide6>=6.4.0
│
│ Tests
├── tests/
│ ├── test_traveller_world_gen.py # Unit tests — mainworld generation
│ ├── test_function_app.py # Unit tests — API endpoints
│ ├── test_world_physical.py # Unit tests — world physical detail
│ ├── test_belt_physical.py # Unit tests — belt physical detail
│ ├── test_hydro_detail.py # Unit tests — hydrographic detail
│ ├── test_moon_gen.py # Unit tests — moon sizing and orbit placement
│ └── test_orbit_gen.py # Unit tests — orbit generation
├── conftest.py # pytest configuration
├── pytest.ini # pytest settings
├── pyrightconfig.json # Pyright type-checker configuration
├── .pylintrc # Pylint configuration
│
│ CI
├── .github/workflows/
│ ├── typecheck.yml # Pyright type-check on every push/PR
│ └── dependency-audit.yml # pip-audit dependency scan
│
│ Documentation
├── docs/
│ ├── AZURE_DEPLOYMENT.md # Full REST API reference (all 13 endpoints)
│ ├── developer-guide.md # Architecture, module reference, compliance notes
│ └── VSCODE.md # VS Code + Claude shared environment setup
│
│ Examples
├── examples/ # Generated sample SVG system maps and HTML output
│
├── LICENSE # MIT Licence + Traveller IP notice
└── README.md # This file
- Python 3.11+
macOS (python.org installer only): If you see
certificate verify failedwhen using TravellerMap lookups, run the one-time certificate installer that ships with Python 3.11:open "/Applications/Python 3.11/Install Certificates.command"This installs
certifiand links it to Python's SSL certificate store. Not required if you installed Python via Homebrew or pyenv.
git clone https://github.com/your-username/traveller-world-gen.git
cd traveller-world-genEach platform has a dedicated script that creates the virtual environment, installs all dependencies, and generates ready-to-use launcher scripts.
macOS / Linux
bash install.shRun it with bash, not by dot-sourcing (. ./install.sh) — the script uses
set -euo pipefail, so any error would close your current terminal if sourced.
After the script finishes, activate the virtual environment in your terminal if
you want to use python / pytest / pylint directly:
source .venv/bin/activateWindows — PowerShell (recommended)
.\install.ps1If you see an execution-policy error, run this first, then retry:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserWindows — Command Prompt
install.batAfter either Windows script finishes, activate the virtual environment if needed:
.venv\Scripts\activateThe generated launcher scripts use absolute paths to the venv interpreter and work without activating the virtual environment.
1. Create a virtual environment (recommended for all use cases)
# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate
# Windows
python -m venv .venv
.venv\Scripts\activate2. Install the package and dependencies for your use case
| Use case | Install command | Notes |
|---|---|---|
| CLI + Python API | pip install -e . |
Installs traveller-world and traveller-mapfetch CLI commands |
| Desktop UI | pip install -e . && pip install -r gen-ui/requirements.txt |
PySide6 bundles Qt — no system packages required |
| Test suite | pip install -e . -r requirements-dev.txt |
Adds pytest, pylint, pyright, httpx2 |
| Azure Functions API | pip install -r azure-api/requirements.txt |
Also requires Azure Functions Core Tools v4 |
| FastAPI server | pip install -r fastapi/requirements.txt |
Includes uvicorn; no extra tooling needed |
3. Run the desktop UI
python gen-ui/app.py4. Run the tests
pytest tests/ -q5. Start the API server locally
Two server implementations are available; both expose the same endpoints.
Azure Functions (port 7071, requires Azure Functions Core Tools v4):
pip install -r azure-api/requirements.txt
cp azure-api/local.settings.json.example azure-api/local.settings.json
cd azure-api && func startFastAPI + uvicorn (port 8000, no extra tooling):
pip install -r fastapi/requirements.txt
cp fastapi/local.settings.json.example fastapi/local.settings.json
cd fastapi && uvicorn app:app --reloadSector is always required — many world names exist in multiple sectors.
# Fetch canonical UWP + stellar data from travellermap.com (sector always required)
traveller-mapfetch --name Regina --sector "Spinward Marches"
# With seed and all secondary world detail
traveller-mapfetch --name Mora --sector "Spinward Marches" --seed 42 --detail
# By hex position within a sector
traveller-mapfetch --sector "Spinward Marches" --hex 1910 --seed 7
# Text summary (default)
traveller-mapfetch --name Regina --sector "Spinward Marches" --format text
# JSON output
traveller-mapfetch --name Regina --sector "Spinward Marches" --json
traveller-mapfetch --name Regina --sector "Spinward Marches" --format json
# Self-contained HTML card
traveller-mapfetch --name Regina --sector "Spinward Marches" --html > regina.html
traveller-mapfetch --name Regina --sector "Spinward Marches" --format html > regina.html# Full system — stellar data, orbits, and mainworld (random, text summary)
python -m traveller_gen.traveller_system_gen
# Named system with a fixed seed
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000
# Include all secondary world and moon profiles
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --detail
# Generate multiple systems (defaults to text output, one block per system)
python -m traveller_gen.traveller_system_gen --count 3 --seed 1000
# JSON output
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --json
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --format json
# Text output (explicit; same as default)
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --format text
# Self-contained HTML card
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --html > ardenne.html
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --format html > ardenne.html
# HTML card with all secondary world and moon profiles
python -m traveller_gen.traveller_system_gen --name Ardenne --seed 1000 --detail --html > ardenne.html# One random world (human-readable summary)
traveller-world
# Named world with a fixed seed
traveller-world --name Cogri --seed 42
# JSON output
traveller-world --name Cogri --seed 42 --json
# Self-contained HTML card
traveller-world --name Cogri --seed 42 --html > cogri.html
# Generate a subsector's worth (5 worlds)
traveller-world --count 5Draw a star system as a visual orbit diagram with arc zones (one per star) and an orbit table.
# Procedurally generated system (random seed, dark background)
python -m traveller_gen.system_map --name Ardenne --out /tmp/ardenne_map.svg
# With a fixed seed for reproducibility
python -m traveller_gen.system_map --name Ardenne --seed 1000 --out /tmp/ardenne_map.svg
# With white background (light theme) instead of dark
python -m traveller_gen.system_map --name Ardenne --seed 1000 --white-bg --out /tmp/ardenne_light.svg
# For multi-star systems, increase canvas width so tables have room
python -m traveller_gen.system_map --name Trinary --seed 5555 --width 2400 --out /tmp/trinary_map.svg
# Default: random seed, name "Unnamed", output to /tmp/traveller_system_map.svg, dark background
python -m traveller_gen.system_mapThe SVG shows:
- Arc zones — one stacked zone per star with orbit arcs scaled to fit the canvas
- Companion stars — displayed as dashed arcs in the primary star's zone for context
- Orbit table — a data table zone below with orbit slots listed per star
- Mainworld — highlighted in the arc zone and marked in the table
Open the SVG in any web browser or image viewer. The map includes all orbital data: world type, orbit#, AU, temperature zone, and notes.
Example summary output:
========================================================
Cogri — B5525A9-7
========================================================
Trade codes : Ni Po
Bases : S
Travel zone : Amber
Gas giant : Yes
--------------------------------------------------------
Starport : B (Good (Refined fuel, spacecraft shipyard, repair))
Size : 5 (8,000 km, 0.45G)
Atmosphere : 5 (Thin)
Temperature : Cold
Hydrograph. : 2 (A few small seas (16-25%))
Population : 5 (Hundreds of thousands)
Government : A (Charismatic Dictator)
Law Level : 9
Tech Level : 7
========================================================
Example JSON output:
{
"name": "Cogri",
"uwp": "B5525A9-7",
"starport": {
"code": "B",
"description": "Good (Refined fuel, spacecraft shipyard, repair)"
},
"size": { "code": 5, "diameter_km": "8,000", "surface_gravity": "0.45G" },
"atmosphere": { "code": 5, "name": "Thin", "survival_gear": "None" },
"temperature": "Cold",
"hydrographics": { "code": 2, "description": "A few small seas (16-25%)" },
"population": { "code": 5, "range": "Hundreds of thousands" },
"government": { "code": 10, "name": "Charismatic Dictator" },
"law_level": 9,
"tech_level": 7,
"has_gas_giant": true,
"bases": ["S"],
"trade_codes": ["Ni", "Po"],
"travel_zone": "Amber",
"notes": []
}from traveller_gen.traveller_system_gen import generate_full_system
system = generate_full_system(name="Ardenne", seed=1000)
print(system.summary()) # full text: stars + orbits + mainworld
print(system.to_json()) # complete JSON
print(system.mainworld.uwp()) # e.g. "C473574-8"
print(system.mainworld_orbit.orbit_number) # e.g. 3.0from traveller_gen.traveller_world_gen import World
from traveller_gen.traveller_system_gen import generate_system_from_world
# Reconstruct a World from a previously generated (or API-returned) JSON dict
world = World.from_dict(world_dict) # tolerates nested or flat code forms
# Generate a full star system around it — UWP and PBG are preserved exactly;
# stellar data and orbits are fresh procedural output;
# temperature is recalculated from the assigned orbital position
system = generate_system_from_world(world, seed=99)
print(system.mainworld.uwp()) # original UWP preserved
print(system.mainworld.temperature) # recalculated from new orbit
print(system.mainworld_orbit.canonical_profile) # canonical UWP stamped on orbit slotfrom traveller_gen.traveller_map_fetch import generate_system_from_map
# By name — sector is always required
system = generate_system_from_map(name="Regina", sector="Spinward Marches", seed=42)
print(system.mainworld.uwp()) # canonical UWP, e.g. "A788899-C"
print(system.mainworld.name) # "Regina"
print(system.summary()) # mainworld orbit row shows canonical UWP
# With all secondary world and moon profiles
system = generate_system_from_map(
name="Mora", sector="Spinward Marches", seed=7, attach=True
)
print(system.to_html(detail_attached=True))
# By hex position (skips name search, more precise)
system = generate_system_from_map(sector="Spinward Marches", hex_pos="1910")from traveller_gen.traveller_world_gen import generate_world
# Generate a random world
world = generate_world(name="Mora")
# Access characteristics directly
print(world.uwp()) # "A867A69-F"
print(world.tech_level) # 15
print(world.trade_codes) # ["Hi", "Ht", "In", "Ri"]
print(world.travel_zone) # "Green"
# Serialise
text = world.summary() # Human-readable text block
data = world.to_dict() # Plain dict (schema-conformant)
json_str = world.to_json() # JSON string (indent=2 by default)
html_str = world.to_html() # Standalone HTML card
# Reproducible generation
import random
random.seed(42)
world = generate_world(name="Regina")The API exposes thirteen HTTP endpoints across three groups.
| Method | Route | Description |
|---|---|---|
GET |
/api/world |
Generate one world |
POST |
/api/world |
Generate one world (parameters in JSON body) |
GET |
/api/world/{name} |
Generate one world; name from URL path |
POST |
/api/worlds |
Batch generation (up to 20 worlds) |
GET |
/api/world/{name}/card |
Standalone HTML display card |
| Method | Route | Description |
|---|---|---|
GET |
/api/system |
Generate a full star system |
POST |
/api/system |
Generate a full star system (parameters in JSON body) |
GET |
/api/system/{name} |
Generate a full star system; name from URL path |
GET |
/api/system/{name}/card |
Standalone HTML system card |
GET |
/api/system/full |
Complete system with all secondary worlds and moons |
POST |
/api/system/full |
Complete system with all secondary worlds and moons (JSON body) |
POST |
/api/system/from-world |
Full system around an existing mainworld JSON; UWP/PBG preserved |
| Method | Route | Description |
|---|---|---|
GET |
/api/map/system |
Fetch world from TravellerMap, generate full system |
POST |
/api/map/system |
Same (parameters in JSON body) |
GET |
/api/map/system/{name} |
World name from URL path |
All JSON responses from mainworld endpoints conform to traveller_world_schema.json.
The /card endpoints return text/html; charset=utf-8.
/api/system/full always attaches full secondary world and satellite detail —
no detail flag required. It also supports a format parameter for choosing
the response type:
format value |
Response type | Content-Type |
|---|---|---|
json (default) |
Complete TravellerSystem JSON | application/json |
html |
Self-contained HTML system card | text/html |
text |
Human-readable text summary | text/plain |
/api/map/system fetches the canonical UWP and stellar classification string
from travellermap.com and uses them as the basis for
full system generation. The mainworld UWP is exact canonical data; the orbital
structure (secondary world positions, moons) is procedurally generated.
sector is always required — many world names exist in multiple sectors.
Identify the world by name + sector, or by sector + hex.
Returns 400 MISSING_PARAM if sector is omitted, 400 INVALID_HEX if hex
is present but not a valid 4-digit hex position (e.g. 1910), 404 NOT_FOUND
if the world cannot be found on TravellerMap, or 502 UPSTREAM_ERROR if
TravellerMap is unreachable. Supports the same detail and format
parameters as the system endpoints.
The other system endpoints accept an optional detail boolean (?detail=true or
{"detail": true} in the request body). When true, every orbit slot in the
response includes a detail object with its secondary world SAH profile
and social codes, and each significant moon carries its own nested detail
object with physical and social data.
Without detail, the system response contains only stellar data, orbital
structure, and the mainworld — no secondary world or satellite information.
Requires Azure Functions Core Tools v4.
pip install -r azure-api/requirements.txt
cp azure-api/local.settings.json.example azure-api/local.settings.json
cd azure-api && func start# Mainworld
curl "http://localhost:7071/api/world?name=Mora&seed=7"
curl "http://localhost:7071/api/world/Regina/card" -o world.html
# System — orbital structure only
curl "http://localhost:7071/api/system?name=Ardenne&seed=1000"
# System — with secondary worlds and satellites
curl "http://localhost:7071/api/system/Ardenne?seed=1000&detail=true"
# System HTML card with full detail
curl "http://localhost:7071/api/system/Ardenne/card?seed=1000&detail=true" -o system.html
# Complete system — always includes all worlds and moons
curl "http://localhost:7071/api/system/full?name=Ardenne&seed=1000"
curl "http://localhost:7071/api/system/full?name=Ardenne&seed=1000&format=html" -o ardenne.html
curl "http://localhost:7071/api/system/full?name=Ardenne&seed=1000&format=text"
# TravellerMap — canonical UWP + procedural orbital structure (sector always required)
curl "http://localhost:7071/api/map/system?name=Regina§or=Spinward+Marches&seed=42"
curl "http://localhost:7071/api/map/system/Mora?sector=Spinward+Marches&seed=7&detail=true"
curl "http://localhost:7071/api/map/system?sector=Spinward+Marches&hex=1910&format=html" \
-o regina.html
# System from existing mainworld JSON — UWP/PBG preserved; fresh stellar + orbital generation
WORLD=$(curl -s "http://localhost:7071/api/world?name=Cogri&seed=42")
curl -X POST "http://localhost:7071/api/system/from-world" \
-H "Content-Type: application/json" \
-d "$WORLD"
# Batch of worlds
curl -X POST "http://localhost:7071/api/worlds" \
-H "Content-Type: application/json" \
-d '{"count": 5, "prefix": "Spinward-", "seed": 1}'For the full Azure API reference including authentication, deployment, and
environment variables see docs/AZURE_DEPLOYMENT.md.
No extra tooling required — just uvicorn.
pip install -r fastapi/requirements.txt
cp fastapi/local.settings.json.example fastapi/local.settings.json
cd fastapi && uvicorn app:app --reloadThe server starts on port 8000. All endpoints are identical to the Azure
version; substitute http://localhost:8000 for http://localhost:7071 and
drop the ?code=<key> authentication suffix (the FastAPI server has no auth).
# Mainworld
curl "http://localhost:8000/api/world?name=Mora&seed=7"
curl "http://localhost:8000/api/world/Regina/card" -o world.html
# System — orbital structure only
curl "http://localhost:8000/api/system?name=Ardenne&seed=1000"
# System — with secondary worlds and satellites
curl "http://localhost:8000/api/system/Ardenne?seed=1000&detail=true"
# Complete system — always includes all worlds and moons; three output formats
curl "http://localhost:8000/api/system/full?name=Ardenne&seed=1000"
curl "http://localhost:8000/api/system/full?name=Ardenne&seed=1000&format=html" -o ardenne.html
curl "http://localhost:8000/api/system/full?name=Ardenne&seed=1000&format=text"
# TravellerMap — sector always required
curl "http://localhost:8000/api/map/system?name=Regina§or=Spinward+Marches&seed=42"
curl "http://localhost:8000/api/map/system?sector=Spinward+Marches&hex=1910"
# Optional WBH rules
curl "http://localhost:8000/api/system/full?seed=42&runaway_greenhouse=true"
curl "http://localhost:8000/api/system?seed=1&detail=true&independent_government=true"
# Batch of worlds
curl -X POST "http://localhost:8000/api/worlds" \
-H "Content-Type: application/json" \
-d '{"count": 5, "prefix": "Spinward-", "seed": 1}'Environment variables (set in fastapi/local.settings.json or shell):
| Variable | Default | Description |
|---|---|---|
TRAVELLER_MAX_BATCH_SIZE |
20 |
Maximum worlds per /api/worlds batch request (1–1000) |
RATE_LIMIT_PER_MINUTE |
100/minute |
SlowAPI per-IP rate limit; uses SlowAPI limit syntax |
OpenAPI docs — FastAPI auto-generates interactive API docs at
http://localhost:8000/docs (Swagger UI) and http://localhost:8000/redoc.
# Create resources (one-time)
az group create --name rg-traveller --location australiaeast
az storage account create --name straveller --resource-group rg-traveller --sku Standard_LRS
az functionapp create \
--resource-group rg-traveller \
--consumption-plan-location australiaeast \
--runtime python --runtime-version 3.11 \
--functions-version 4 \
--name traveller-world-gen \
--storage-account straveller \
--os-type Linux
# Deploy
func azure functionapp publish traveller-world-genAuthenticate deployed requests with a function key:
curl "https://traveller-world-gen.azurewebsites.net/api/system/Mora?detail=true&code=<key>"| Variable | Default | Description |
|---|---|---|
TRAVELLER_MAX_BATCH_SIZE |
20 |
Maximum worlds per batch request (accepted range: 1–1000) |
APPLICATIONINSIGHTS_CONNECTION_STRING |
(empty) | Application Insights telemetry |
The test suite runs with pytest and requires no live Azure runtime — the
azure-functions SDK is stubbed automatically if not installed.
pip install -e . -r requirements-dev.txt
pytest tests/ -v1168 tests across 7 files. All pass. Pylint 10.00/10 on all core generation modules.
test_traveller_world_gen.py (807 tests, 71 classes)
- All dice helper functions and clamp behaviour
- Traveller hex digit conversion (0–9, A–G)
- Every generation step in isolation with mocked dice
- All 18 trade code criteria (positive and negative cases)
- All Amber zone triggers and boundary values
- Base generation per starport class, including highport and corsair DMs
World.to_dict()— structure, types, and all sub-objectsWorld.to_json()— round-trip fidelity, indent modesWorld.to_html()— HTML structure, all TL era labels (regression tests for the Pre-Stellar/Early Stellar boundary), survival gear danger highlighting, HTML escapingWorld.from_dict()/_validate_world_codes()— valid and invalid inputs- JSON Schema validation against
traveller_world_schema.json(requirespip install jsonschema) - Integration tests: range bounds, uninhabited world invariants, seed reproducibility
TestGasGiantOrbitSlot—gg_sahfield onOrbitSlot,_gg_diameter()helper, satellite size constraint, satellite note in mainworld record
test_function_app.py (117 tests, 17 classes)
- All HTTP endpoints: 200 responses, parameter parsing, content types
- All error codes: invalid seed, name too long, bad JSON body, count too large, internal error mocking
- Schema validation of all JSON responses
- Seed determinism across endpoints
- Batch sequencing consistency
- Physical detail in system responses
test_world_physical.py (122 tests, 22 classes)
- Physical world generation: composition, density, diameter, mass, gravity, escape velocity
- Axial tilt including extreme-tilt sub-table
- Rotation period and tidal lock status (all 11 outcomes, broken-lock check)
- Tidal lock DMs: eccentricity, moon size, multi-star, planet-to-moon lock
apply_moon_tidal_effects()integration- Basic mean temperature: orbital DMs, atmosphere DMs, table lookup, extrapolation
- Seismic stress: residual stress, tidal heating factor, seismic temperature
test_belt_physical.py (49 tests, 8 classes)
- Belt span, composition percentages, bulk formula, resource rating
- Significant body counts: Size 1 and Size S planetoids
test_hydro_detail.py (29 tests)
- Surface liquid percentages for all hydrographic codes
test_moon_gen.py (30 tests, 8 classes)
- Moon quantity rolls, adjacency DMs, sizing, ring consolidation
- Moon orbit placement: Hill sphere, orbit range, PD distances, period
- Moon SAH and social detail
test_orbit_gen.py (14 tests, 3 classes)
- Orbit generation integration: world counts, spread, slot placement, anomalous orbits
All 13 steps follow the Traveller 2022 Core Rulebook exactly.
| Step | Characteristic | Formula |
|---|---|---|
| 1 | Size | 2D−2 |
| 2 | Atmosphere | 2D−7 + Size (min 0; forced 0 for Size 0–1) |
| 3 | Temperature | 2D + Atmosphere DM |
| 4 | Hydrographics | 2D−7 + Atmosphere + DMs (size, temp, atm type) |
| 5 | Population | 2D−2 |
| 6 | Government | 2D−7 + Population (0 if Pop = 0) |
| 7 | Law Level | 2D−7 + Government (0 if Pop = 0) |
| 8 | Starport | 2D + Population DM → A/B/C/D/E/X |
| 9 | Tech Level | 1D + Starport/Size/Atm/Hydro/Pop/Gov DMs |
| 10 | Bases | 2D per type per starport class (Highport & Corsair DMs applied) |
| 11 | Gas Giant | 2D ≤ 9 → present |
| 12 | Trade Codes | Table lookup (all 18 codes) |
| 13 | Travel Zone | Amber if Atm ≥ 10, Gov 0/7/10, or Law 0/9+ |
When a mainworld is generated in orbital context (system endpoints), step 3 is
replaced by generate_temperature_from_orbit(), which derives temperature from
the world's HZ deviation rather than a random roll. When the mainworld orbit is a
gas giant, the mainworld is generated as a satellite: size is clamped to
[1, gg_diameter−1] (WBH p.57) and a note is added to the world record.
| Procedure | Details |
|---|---|
| Primary star type | Spectral class (O B A F G K M) and luminosity class (Ia Ib II III IV V VI) rolled from WBH tables; Brown Dwarfs and White Dwarfs handled |
| Star properties | Mass, temperature, diameter, and luminosity interpolated from WBH lookup tables by spectral type and subtype (0–9) |
| System age | Rolled for primary; constrained by main-sequence lifespan; shared across all stars |
| Multiple stars | Close, Near, and Far secondaries; tight Companion pairs (e.g. Aa/Ab) |
| Non-primary typing | Random, Lesser, Sibling, or Twin procedure; mass ordering enforced (candidate.mass < parent.mass) |
| Non-primary properties | Same interpolation tables as primary |
| Step | Procedure | Details |
|---|---|---|
| World counts | Gas giants, belts, terrestrials | 2D rolls with DMs for luminosity class |
| Empty orbits | Step 4 | 2D roll; result > 9 → that many empty slots added |
| MAO | Minimum Allowable Orbit# | Interpolated from WBH table by spectral type, subtype, and luminosity class |
| HZCO | Habitable Zone Centre Orbit# | √(combined luminosity) × 3.0 |
| Baseline number | Step 3 | 2D + DMs for luminosity class, companion presence, world count |
| Baseline orbit | Step 3a/3b/3c | 3a: HZ world present; 3b: cold system (all worlds beyond HZ); 3c: hot system (all worlds inside HZ) |
| Spread | Step 5 | (baseline_orbit − MAO) / baseline_num; floored and capped by available range |
| Slot placement | Step 6 | MAO + spread + (2D−7)×spread/10 per slot; additive gap check |
| World type assignment | Step 8 | Pool of gas giants → belts → terrestrials placed into slots; remainder as terrestrial |
| Mainworld selection | Step 9 | Scored by HZ proximity, temperature zone, world type, and star role |
Multiple-star systems allocate worlds to each star proportionally by available
orbital range. Primary star's max_o is reduced to companion.orbit_number − 1.0
when a Close/Near/Far secondary is present.
| Procedure | Details |
|---|---|
| SAH profile | Size, Atmosphere, Hydrographics rolled for every non-mainworld orbit slot and significant moon |
| Population cap | mainworld.population − 1D rolled once per system; applied as a ceiling to all secondary worlds and moons |
| TL viability check | minimal_sustainable_TL(atmosphere) > mainworld_TL → uninhabited regardless of population roll |
| Government | Dependent procedure (WBH Case 1): 1D on Secondary World Government table |
| Law Level | 1D−3 + Government |
| Tech Level | 1D−1 + Population DM |
| Spaceport | Y/H/G/F scale (not the CRB A–X starport scale) |
| Gas giants | Never directly inhabited; moons may be |
| Belt SAH | Fixed at 000; atmosphere 0 used for TL viability check (TL ≥ 8 required) |
| Procedure | Details |
|---|---|
| Quantity | Size 1–2: 1D−5; Size 3–9: 2D−8; Size A–F: 2D−6; Small GG: 3D−7; Medium/Large GG: 4D−6; DM−1 per die if Orbit# < 1.0 |
| Result = 0 | One significant ring (R) instead of moons |
| Terrestrial moon sizing | 1D picks range: 1–3 → size S; 4–5 → D3−1 (0=ring); 6 → (parent−1) − 1D |
| Gas giant moon sizing | 1D picks range: 1–3 → size S; 4–5 → D3−1; 6 → special table (1D: 1–3→1D, 4–5→2D−2, 6→2D+4) |
| Twin/near-twin check | If moon size = parent−2: roll 2D; 2 → near-twin (parent−1), 12 → twin (parent) |
| Moon size cap | Moon cannot exceed parent size |
| Ring consolidation | Multiple rings on one planet collapsed to a single R0N entry |
| Moon SAH and social | Full secondary world procedure using parent orbit's HZ deviation |
The following WBH procedures are explicitly deferred:
| Feature | WBH pages |
|---|---|
| Secondary world independent government (Case 2) | p. 162 |
| Secondary world classifications (Colony, Freeport, Mining, etc.) | p. 163 |
| Tidal Stress Factor contribution to seismic stress | p. 126 |
| Post-stellar special circumstances (neutron stars, black holes, pulsars) | pp. 219+ |
traveller_world_schema.json is a JSON Schema draft 2020-12 document
describing the complete output of World.to_dict() / World.to_json().
Key constraints:
"additionalProperties": falsethroughout- UWP validated by regex
^[ABCDEX][0-9A-G]{6}-[0-9A-G]$ starport.code— enum ofA B C D E Xbasesitems — enum ofC H M N Strade_codesitems — enum of all 18 valid codestemperature— enum of 5 valid categoriestravel_zone— enum ofGreen Amber Red- All numeric codes have explicit
minimum/maximumconstraints
This project was developed with the assistance of Claude (Anthropic), an AI assistant.
Claude contributed to the architecture, implementation, tests, and this README across an interactive development session in which rulebook pages were provided as source material. All generation steps, lookup tables, and descriptions were verified against the Traveller 2022 Core Rulebook and World Builder's Handbook during the session.
The human author reviewed, directed, and is responsible for the published code.
This project is released under the MIT Licence.
Traveller IP notice: This software implements rules and procedures from the Traveller roleplaying game. Any use of this software in connection with the Traveller intellectual property is subject to Mongoose Publishing's Fair Use Policy, which prohibits commercial use. You may not use this software commercially in connection with the Traveller IP without an appropriate licence from Mongoose Publishing.
The Traveller game in all forms is owned by Mongoose Publishing. Copyright 1977–2025 Mongoose Publishing. All rights reserved.
This project is an unofficial fan work and is not affiliated with or endorsed by Mongoose Publishing.