A deterministic, procedural galaxy generator that creates entire galaxies from a single seed - no data storage required. Query any position in a galaxy and get back the same stars, systems, and planets every time.
I used several LLM models to help me generate parts of this project. I toiled with the idea of releasing an open source library in which I used an LLM to assist, but in the end, I felt that giving back via open source is a sound way to make sure that the usage is perhaps more ethical, as the models were trained on our collective history.
I've been working on this project off and on for over a decade in various languages. I've always loved the idea of procedural galaxies, and love Elite Dangerious, No Man's Sky, and the like. I went through a few different ideas on how to implement this in the background while doing work, life, and other side projects. Recently while driving I realized my current implementation was a tangled mess and started thinking about the core math of the algorithm. I realized I needed way more simplicity. The way I have drawn spirals in the past was with various simple geometric functions. But a galaxy is a collection that ends up having density in some spots, and not others, based on patterns that have emerged over an enormous amount of time. So I thought, let's describe the shape with density functions. In the last attempt, I had a crazy 23 layer deep octree system, where the octree path was the address to the star system that might exist, in that work I realized 0.25ly cells were a reasonable size. So now, to determine if a star is there we ask the combination of density functions at that point, and if it's above a threshold, we generate a star. We can't sample every star, or store every star, but we can sample larger chunks based on the current required view to get a galactic structure. Then I thought how one might practically use this library/API, and there's a number of ideas that could be great, but I settled on getting a general idea of galactic shape, and then querying for all nearby stars as well, and then allowing for selecting a system. There's a lot of psuedo science in this, but it's getting closer and it's a lot of fun! The system mechanics are not even close, and it's not like density functions are how galaxies are created, but it's an way to think about it.
2025-12-27 I realized that the standard milky way galaxy was generating 400,000 billion stars with my density formulas, quite a lot more than expected. When I reduced the density factor to compensate, the galactic structure sampling resulted in 5000 stars, not 500,000. I needed to adjust the sampling resolution and density functions to achieve the desired star count. Now, the galactic structure samples with a higher density, but since there would be a mismatch between the galactic structure stars and the nearby stars, I now include both sets of stars in the final result for nearby stars, to ensure that every star is still clickable and discoverable across all zoom levels.
Warning: This is a pre-alpha project and does not claim any sort of scientific accuracy.. but could hopefully be used as a simulation starting point some day.
MEI generates galaxies procedurally using density functions and deterministic hashing. The entire galaxy exists implicitly - stars are computed on-demand based on position, not stored. This means:
- Infinite detail: Zoom from galactic scale (100,000 ly) down to individual star systems
- Zero storage: A galaxy with billions of potential stars requires only a 64-bit seed
- Perfect determinism: Same seed + same position = same stars, always
- Multi-client consistency: Different viewers querying the same galaxy see identical data
The galaxy's stellar density at any 3D position is computed using astrophysically-inspired functions. For spiral galaxies (like the Milky Way), this combines:
Total Density = Central Bulge + Bar + Disk × Spiral Arms + Halo
Components:
- Central Bulge: Oblate spheroid with Gaussian falloff from center
- Bar: Elongated structure at configurable angle (Milky Way: ~25°)
- Disk: Exponential radial profile × sech² vertical profile (thin + thick disk)
- Spiral Arms: Logarithmic spirals with Gaussian cross-section, emerging from bar ends
- Halo: Very sparse spherical distribution extending far beyond the disk
Different galaxy types (elliptical, irregular) use different density functions:
- Elliptical: Multi-blob de Vaucouleurs R^(1/4) profiles
- Irregular: Gaussian blobs with deterministic clumpy noise
Density Threshold: All density functions apply a min_density_threshold (default 0.0001) to filter out extremely sparse regions, ensuring consistency between galactic structure visualization and nearby star queries.
Space is divided into a grid of 0.25 light-year cells. For each cell:
- Compute density at the cell's center position
- Hash the cell coordinates with the galaxy seed to get a deterministic random value
- Probabilistic placement: If
hash_roll < f(density), the cell contains a star - Star properties: The same hash determines star type, mass, temperature, etc.
// Deterministic cell seed
cell_seed = hash(galaxy_seed, cell_x, cell_y, cell_z)
// Does this cell have a star?
roll = cell_seed % 10000 / 10000.0 // 0.0 to 1.0
probability = sqrt(density) * 0.02
has_star = roll < probabilityThis means the same cell always produces the same star (or no star), regardless of when or how you query it.
Different query strategies for different scales:
| Query Type | Radius | Method | Use Case |
|---|---|---|---|
| Exhaustive | 0-32 ly | Cell iteration (0.25 ly) | Nearby stars, background rendering |
| Galactic Structure | Full galaxy | Block sampling (100 ly) | Galaxy overview, 500k stars |
| Hybrid | 0-32 ly | Exhaustive + Structure | Ensures all stars are discoverable |
Hybrid approach: Nearby queries return both exhaustive cell-based stars AND galactic structure samples, then deduplicate by cell. This ensures:
- Every star visible in the galactic view is clickable up close
- No "ghost stars" that appear in structure but can't be selected
- Consistent star counts across zoom levels
When you query a specific star, its planetary system is generated deterministically from the star's ID:
system_seed = hash(star_id)
// Determines: number of stars, planets, moons, frost line, etc.This is a Cargo workspace with three main packages:
matter-energy-information/
├── src/mei/ # Core library (mei crate)
│ ├── generation/ # Procedural generation
│ │ ├── generator.rs # Galaxy generator (density functions, cell system)
│ │ └── system_generator.rs # Star system generator (planets, moons, belts)
│ ├── space_objects/ # Data structures
│ │ ├── galaxy.rs # Galaxy, GalaxyParams, GalaxyType
│ │ ├── star.rs # Star, StarType
│ │ ├── planet.rs # Planet, PlanetType, Moon
│ │ └── orbit.rs # Orbital mechanics
│ ├── api/ # HTTP API layer (optional)
│ │ ├── galaxy_api.rs # Galaxy query endpoints
│ │ └── routes.rs # Actix-web routes
│ └── util/ # Math utilities (Vec3, noise, etc.)
├── src/main.rs # mei-server binary (HTTP API)
├── mei-viewer/ # Bevy-based 3D viewer (Rust)
│ ├── src/main.rs # Standalone Bevy application
│ └── src/viewer_ui.rs # bevy_egui UI panels
├── generator_config.toml # Root generator config
└── viewer.html # Three.js web viewer
MEI includes multiple viewers for exploring galaxies at different scales:
Standalone native 3D viewer built with Bevy game engine and bevy_egui for UI.
Features:
- Real-time galaxy exploration with fly camera
- Point cloud rendering (500k galactic structure + nearby stars)
- Star selection and system inspection
- Simple UI for:
- Seed input and generation
- Nearest stars list (clickable)
- Goto position (fly to coordinates)
- Flight mode controls
- Selected star details (type, mass, luminosity, planets, moons)
- Mouse picking with screen-space hash grid
- Dynamic nearby star updates (debounced)
Run:
cargo run -p mei-viewer --releaseControls:
- WASD/QE: Movement (fly camera)
- Mouse: Look around (hold right-click)
- Scroll: Adjust speed
- Click star: Select
- UI panels: Interact with egui
A web-based viewers using Three.js (requires HTTP server).
Run:
# Start API server
cargo run --bin mei-server --release
# Serve web files
npx http-server . -p 8080
# Open browser
# http://localhost:8080/viewer.html (main viewer)There is a Godot extension and viewer available in a separate repository. See mei-godot for more information.
Seed 0 is special - it generates a Milky Way clone with parameters based on current astronomical estimates:
| Parameter | Value | Real MW |
|---|---|---|
| Disk radius | ~50,000 ly | ~50,000 ly |
| Bar length | 15,000 ly | ~13,500 ly |
| Bar angle | 25° | 25-30° |
| Spiral arms | 4 | 4 major |
| Thin disk height | 300 ly | 300-400 ly |
| Thick disk height | 1,000 ly | 1,000 ly |
This is an ongoing project with some rough edges and known problems:
mei-viewer (Bevy)
- Star selection is janky: Picking stars in 3d is incredibly hit or miss, especially with only using point rendering, it's hard to tell when something is actually "close"
Web Viewers
- Limited interactivity: Primarily for visualization - can't select stars or enter system view
- Performance issues with large datasets: Struggles with 500k+ stars on some browsers
- Density threshold artifacts: In very sparse regions (outer halo, between arms), you might see small gaps where galactic structure shows stars but nearby queries don't return them (we recently fixed the worst of this, but edge cases may remain)
- Star count variability: Different seeds can produce galaxies with wildly different star counts even with similar parameters
- System generation is simplified: Planetary systems use simplified orbital mechanics - no resonances, no Lagrange points, no complex multi-body dynamics
- Binary star orbits are fake: Binary/trinary systems show multiple stars but they don't actually orbit each other
- Nearby star queries can be slow: 32 ly radius queries check ~4 million cells in worst case - debouncing helps but large movements can cause lag. I use 32 because it generates about 5000 stars on average which is fairly close to a night sky from Earth for humans, or well enough for an illusion. The algorithm needs work but we can probably increase this in time.
- Galactic structure generation not cached: Every time you change seeds, the full 500k star structure is regenerated which takes some time, normally under a minute for initial load.
- No LOD system: All stars render as same-size points regardless of distance
The HTTP server exposes these endpoints:
POST /api/galaxy/{seed}/structure - Get galactic structure (stars + metadata)
POST /api/galaxy/{seed}/nearby - Query nearby stars around a position
POST /api/galaxy/{seed}/system - Get star system details
Generator behavior is controlled by generator_config.toml:
# Cell size for spatial quantization (light-years)
cell_size = 0.25
# Star probability scale factor (controls density)
star_probability_scale = 0.002
# Block size for galactic structure sampling
structure_block_size = 100.0
# Samples per block for structure
structure_samples_per_block = 1
# Maximum radius for nearby star queries (light-years)
nearby_max_radius = 32.0
# Scramble stride for even distribution
scramble_stride = 2654435761Key parameters:
cell_size: Fundamental unit (0.25 ly = reasonable stellar spacing)star_probability_scale: Tuned for ~400 billion Milky Way starsnearby_max_radius: Clamps nearby queries (32 ly = good performance)structure_block_size: Larger = faster but coarser galactic structure
# Rust toolchain
rustup default stable# Run tests
cargo test --lib
# Build script (builds server + Bevy viewer)
./build-all.sh# 1. Try the Bevy viewer (fastest to start)
cargo run -p mei-viewer --release
# 2. Or start HTTP server for web viewers
cargo run --bin mei-server --release &
npx http-server . -p 8080
# Open http://localhost:8080/viewer.htmlMIT

