Farlight needed infinite variety. The concept was simple: explore randomly generated abandoned spaceships, each one unique. But how do you procedurally generate believable spaceships that feel like real places, not random noise?
Why Procedural Generation?
Hand-crafting levels is beautiful but time-consuming. Procedural generation lets solo devs create content at scale. For Farlight, it meant:
- Infinite replayability without repeating layouts
- Organic exploration where no playthrough is identical
- Discovery-driven gameplay that rewards curiosity
- Feasibility for a one-person team
The challenge? Making it feel authored, not random.
The Grammar-Based Approach
I use a "ship grammar"ārules that define valid ship structures. Think of it like sentence structure in language. A ship has:
- Core modules: Bridge, engine room, life support (required)
- Utility modules: Storage, medbay, labs (contextual)
- Connector modules: Hallways, junctions, airlocks
- Optional modules: Observation decks, crew quarters
The generator starts with core modules and expands outward, ensuring every ship is navigable and logical.
Wave Function Collapse
I use a simplified Wave Function Collapse algorithm. It works by:
- Starting with a grid of "superposition" cells (all possibilities)
- Collapsing one cell to a specific room type
- Propagating constraintsāadjacent cells update their possibilities
- Repeat until all cells are resolved
This ensures rooms connect properly. A hallway entrance must have a hallway or room entrance adjacent, never a wall.
Module Placement Rules
Constraints make spaceships believable:
- Bridge: Must be accessible from main corridor, ideally near front of ship
- Engine room: Always at the rear, connects to power conduits
- Airlocks: Only on exterior hull, never internal
- Labs: Prefer isolation, limited connections
- Crew quarters: Cluster together, near mess hall
These rules emerged from research and sci-fi logic. Real spacecraft design informed the constraints.
Seeded Generation
All generation uses a seed value. Same seed = same ship. This lets players share interesting discoveries:
- Seed stored as a short code players can share
- Deterministic generation means "Ship-XJ3K2" is identical for everyone
- Speedrunners can optimize specific seeds
- Bug reports include seeds for reproducibility
Detail Pass: Making It Lived-In
Once the layout is generated, a detail pass adds environmental storytelling:
- Debris: Placed in damaged sections, never blocking critical paths
- Logs: Audio logs placed in relevant rooms (medical log in medbay)
- Damage: Hull breaches near exterior, electrical failures near power systems
- Items: Resources spawn based on room function
This pass uses separate probability tables for each room type, making discoveries contextual.
Navigation and Flow
Good level design has flowāa sense of progression. For procedural generation, this is hard. I use:
- Objective markers: The generator places a "goal" in a meaningful location
- Breadcrumb trail: Lights and signs guide toward main objectives
- Loops and shortcuts: After finding keycards, players can unlock shortcuts
- Landmarks: Visually distinct rooms (observation deck, vertical shafts)
Performance Considerations
Generating a ship needs to be fast. Optimization strategies:
- Lazy loading: Only generate/load rooms near the player
- Occlusion: Sealed doors hide unloaded areas
- Chunking: Ship divided into chunks that stream in
- Mesh batching: Similar corridor sections share geometry
Generation happens during the loading screen. Most ships generate in under 2 seconds.
Testing Procedural Content
Testing procedural games is weird. You can't test every possibility. Instead:
- Automated validation: Check that all ships meet basic rules
- Exploratory testing: Manually test random seeds, look for patterns
- Edge case seeds: Find seeds that break and fix the generator
- Playtest loops: Test 50+ seeds to find common issues
I found bugs where specific room combinations caused dead ends or unreachable areas. These required constraint tweaking.
Balancing Variety and Coherence
Too much variety creates visual chaos. Too little feels repetitive. I balance by:
- Limiting room types to 12 distinct modules
- Reusing corridor assets but varying layout
- Color-coding sections (blue for science, red for engineering)
- Maintaining consistent art style across all modules
Players should feel variety without feeling lost in randomness.
Lessons Learned
Procedural generation isn't "free content"āit's trading design time for systems engineering. Key lessons:
- Constraints create creativity: Strict rules produce better variety than pure random
- Playtest obsessively: Bugs emerge from unexpected combinations
- Fail gracefully: If generation fails, retry with relaxed constraints
- Author the pieces: Hand-craft modules that fit together procedurally
- Guide the player: Procedural doesn't mean confusing
The Future of Procedural Design
Procedural generation is a tool, not a solution. The best procedural games combine handcrafted elements with algorithmic variety. Farlight's spaceships feel believable because the modules are carefully designed, even if the arrangements are random.
I'm excited about machine learning-assisted generationāimagine training an AI on good level design and having it generate new levels that feel authored. We're not there yet, but the potential is enormous.
Building Farlight taught me that procedural generation is a design philosophy, not just a technique. It's about creating systems that surprise you as the developer, systems that generate content you didn't explicitly design but that still feels intentional.
And when you boot up Farlight and explore a spaceship that has never existed before and will never exist again? That's when procedural generation feels like magic.
ā Back to Blog