What it is

The world is an infinite 2D plane populated with biome-specific terrain, hub-and-spoke navigation corridors, and procedurally generated nebula backdrops. Each run loads a planet whose biome drives terrain shape pools, hub topology, enemy set, fog density, and post-processing mastering. Player XP feeds both a per-run level curve (drives reward cards) and a per-planet meta-progression track (drives unlocks).

Stats / tables

Planets — gameplay-relevant parameters

IDNameBiomeEnemy setBossFog alphaEnemy multSpawn grace (s)Shadow multLeaderboard
12LANDING SITElanding_sitebugspacemaker0.151.011.0no
21SUNRISE CITYsunrise_citycityfirst_lady0.781.012.0no
3THE VOIDSTARthe_voidstarbugscenotaph0.002.001.0yes
30SOLARISlanding_sitebugs_mortariron_throne0.201.011.0no
31SPEEDWAYlanding_sitebugs_shooterspire0.101.011.0yes
32EDEN-5landing_sitebugs_chargergrand_procession0.201.011.0no
33OLD EARTHold_earthbugs_sniperringmaster0.251.011.0no
34NETWORK STATIONlanding_sitebugs_fieldforeman0.151.011.0no
35DELPHIdelphibugs_racerapex0.151.011.0no
36DESOLATIONlanding_sitebugs_heavy0.151.011.0no
37OBELISKlanding_sitebugs_mixed0.101.011.0no
99OBSIDIAN SPIREobsidian_spirebugs0.051.511.5no

Planet order in hub swiper: 12, 21, 3, 30, 31, 32, 33, 34, 35, 36, 37. Planet 99 is not in the swiper.

Biomes — terrain and hub topology

BiomeLevel radiusDensityIntensityTerrain mixHub min distHub max distHubs/ringHub clearSpoke widthScatter only
landing_site25000.850.70asteroids40010006200150no
sunrise_city25000.700.60buildings00000yes
the_voidstar25000.600.55voidstar_mix50012005200150no
obsidian_spire25000.550.50pillars50012005200150no

Terrain shapes — collision and visual radius

Shape IDTypeVertsBase radiusScale minScale maxEffective radius (px)
asteroid_smpolygon5751.01.575–112
asteroid_mdpolygon6851.01.685–136
asteroid_lgpolygon7981.01.698–156
asteroid_xlpolygon81001.01.8100–180
rock_shardpolygon5601.01.360–78
building_1x1sprite42771.01.0277
building_2x1sprite43221.01.0322
building_1x2sprite42841.01.0284
building_2x3sprite43851.01.0385
pillar_smsprite45761.01.0576
pillar_mdsprite47681.01.0768
pillar_lgsprite49601.01.0960

Terrain mix pools — cluster shape weights

MixTierShapeWeight
asteroidscenterasteroid_xl60
asteroidscenterasteroid_lg40
asteroidsmediumasteroid_lg40
asteroidsmediumasteroid_md40
asteroidsmediumrock_shard20
asteroidssmallasteroid_sm40
asteroidssmallasteroid_md30
asteroidssmallrock_shard30
buildingscenterbuilding_2x350
buildingscenterbuilding_2x130
buildingscenterbuilding_1x220
buildingsmediumbuilding_1x135
buildingsmediumbuilding_2x130
buildingsmediumbuilding_1x225
buildingsmediumbuilding_2x310
buildingssmallbuilding_1x150
buildingssmallbuilding_2x125
buildingssmallbuilding_1x225
voidstar_mixcenterpillar_md70
voidstar_mixcenterasteroid_xl18
voidstar_mixcenterasteroid_lg12
voidstar_mixmediumpillar_md40
voidstar_mixmediumpillar_sm28
voidstar_mixmediumasteroid_lg14
voidstar_mixmediumasteroid_md12
voidstar_mixmediumrock_shard6
voidstar_mixsmallpillar_sm36
voidstar_mixsmallpillar_md30
voidstar_mixsmallasteroid_sm14
voidstar_mixsmallasteroid_md10
voidstar_mixsmallrock_shard10
pillarscenterpillar_md60
pillarscenterpillar_sm40
pillarsmediumpillar_md55
pillarsmediumpillar_sm45
pillarssmallpillar_sm60
pillarssmallpillar_md40

Hub-spoke pattern types

PatternHubs per chunkSpoke algorithmChunk size range (px)
grid1, centeredorthogonal H/V only1500–3500
zigzag1, alternating L/R by rowmaximal planar1500–3000
ringscenter + concentric ringsmaximal planar(outer × 2 + 400) × 1.0–1.1
chaotic1–4, Poisson-discmaximal planar2000–4000

Hub-spoke generation constants

StatValue
Max spoke distancechunkSize × 1.8
Grid spoke axis tolerancechunkSize × 0.4
Hub minimum separationhubRadius × 2.5
Chaotic placement attempts per chunk80
Rings: min count1
Rings: max count3
Rings: outer radius range20%–45% of chunk size
Zigzag amplitude range15%–40% of chunk size
Grid hub jitter±8% of chunk size
Zigzag X jitter±6% of chunk size; Y ±8%
Chaotic marginhubRadius + 5 px
Hub radius jitter90%–110%
Rings center-hub radius multiplier1.2
Precompute grid radius (chunks each direction)10

Legacy ring-based hub placement (non-zone-aware)

StatValue
Central START hub position(0, 0)
Central START hub radius300
Central START hub clearRadius600
Hub radial jitter±30%
Hub angular jitter±40% of step
Ring outer cutoff95% of level radius
Spoke typescenter→ring1; ringN→ringN+1 nearest; same-ring adjacency
Same-ring cross spoke width multiplier0.75
Hub bounding radiusclearR × 0.7
Location influence radiusmax(clearR × 1.2, 2000)
Event slots per location2

Zone classification

ZoneDefinitionLit variant trigger
hub_darkInside any hub circle (point distance ≤ hub.r)hub illuminated
hub_litInside any hub circleyes
spoke_darkWithin spokeHalfWidth of any spoke center segmenteither endpoint hub lit
spoke_litWithin spokeHalfWidth of any spoke center segmentyes
wildsEverywhere elsenever illuminated

Priority order: hub > spoke > wilds. Default zone-grid cell size: 16 px. Spoke bounding-box pad for live lookup: 50 px.

Terrain generation phases

PhaseAction
ACluster-based fill on a grid (center + medium ring + small ring per cluster)
BCarve hubs and spokes (delete overlapping terrain) — skipped in zone-aware mode
CRemoved (no boundary walls; infinite world)
DDisabled (floaters disabled; terrain is static)

Cluster fill — density-driven parameters

ParameterAt intensity 0.0At intensity 1.0
Cluster skip rate1.00.0
Cluster grid step (px)900120
Medium pieces per cluster (intensity < 0.3)0
Medium pieces per cluster (intensity ≥ 0.3)floor((1 + rand×3) × intensity)
Small pieces per cluster (intensity < 0.4)0
Small pieces per cluster (intensity ≥ 0.4)floor((2 + rand×4) × intensity)
Overlap pad between asteroid edges (px)750
Medium ring distance from center (px)center.r + 40 to center.r + 90same
Small ring distance from center (px)center.r + 100 to center.r + 180same
Cluster center jitter±50% of stepsame

Sunrise City grid layout

StatValue
Block size (interior, px)1000
Road width (px)200
Cell pitch (block + road, px)1200
Building sub-grid step (px)500
Building sub-grid skip rate10%
Spawn clear radius (px)600
Building center jitter±15 px
Overlap radius (rectangular footprint)30% of bounding radius
Roads registered as spokes (both axes)yes
City start hub(0, 0), radius 300, clearRadius 600

Dynamic terrain expansion

StatValue
Super-chunk size (px)800
Expansion radius around player (px)2200
Skip rate (natural gaps)3%
Center cluster jitter±40% of super-chunk
Mediums per expansion cluster3–5
Smalls per expansion cluster4–8
Garbage-collect distance from player (px)3000
Clear check vs hub clearRadius+ super-chunk × 0.7
Clear check vs spoke widthspokeW × 0.5 + SC × 0.5
Clear check vs event clearanceev.radius + 80 + SC × 0.7
Crate spawn chance per expansion super-chunk85%
Crates per firing super-chunk2–4
Crate placement RNG seed termseed + floor(playerX) × 33391 + floor(playerY) × 77317
Chunk regen on GCyes — generated-chunk key deleted
Spatial index chunk size (px)1024

Event placement on the playable area

StatValue
Cell width (px)500
Cell height (px)350
Cell skip rate34%
Cell center jitter±40% of cell
Reject distance from origin (px, min)700
Reject distance from origin (px, max)95% of level radius
Hub-clear test radiusclearRadius × 0.7
Spoke-clear test radiusspokeW × 0.35
Event-to-event edge minimum gap (px)400
Event radius size jitter±20%
Terrain clear pad around event (px)event.radius + 80
Overlap-cleanup pad between events (px)150
Sub-event spawn chance per non-start hub45%
Sub-event placement jitter (px)±100
Crates per hub20–50
Crate scatter radius around hub (px)±350

Per-run XP curve (level-up rewards)

Cumulative XP at each level, with per-level cost:

LevelCumulative XPCost
00
15050
2150100
3300150
4500200
5750250
61050300
71400350
81800400
92250450
102750500
113325575
124000675
134800800
145750950
1569001150
1683001400
17100001700
18121002100
19147002600
20179003200
21+previous + previous_cost × 1.12 (rounded up)compounding

Per-planet meta-progression track

StatValue
Levels per planet10
Base XP thresholds (cumulative)100, 250, 500, 800, 1200, 1700, 2300, 3000, 4000, 5500
Planet 12 XP multiplier1.0
Planet 21 XP multiplier1.5
Planet 3 XP multiplier2.0
Planets 30–37 XP multiplier1.0
Planet 12 base run XP10
Planet 21 base run XP15
Planet 3 base run XP20
Planets 30–37 base run XP10
Max level10
Starting level1

Reward layout per planet:

LevelReward typeRarity
1custom_eventcommon
2ship_commoncommon
3weapon_1uncommon
4ship_uncommonuncommon
5alt_bossrare
6ship_rarerare
7planet_masterepic
8ship_epicepic
9weapon_2legendary
10global_bufflegendary

Level-up reward rarity roll (per card)

RarityWeightEffect multiplier
common501.00
uncommon301.25
rare151.50
epic41.75
legendary12.00

Luck bias coefficient per tier: 0, 0.01, 0.02, 0.03, 0.04. Each non-common weight is multiplied by 1 + effectiveLuck × bias. Effective luck = ship.luck × (1 + ship.luckMult).

Level-up card pool slot selection

StatValue
Weapon-upgrade slot chance35%
Modifier slot chance65%
Merge cap (cards reserved when eligible)min(merge candidates, ceil(count/2))
Max weapon cardsunique upgradeable weapons
Max modifier cardsunique candidate modifiers
Fallback when chosen pool exhaustedswitch to other pool

Weapon-chest fractional upgrades

Chest rarityLevel increment
common0.20
uncommon0.40
rare0.60
epic0.80
legendary1.00

Max weapon level: 20.

Nebula archetype catalog (background visuals)

Catalog: 100 baseline archetypes split into 11 thematic zones, plus per-planet overrides.

ZoneIndex rangeTheme
I0–7Outer Acheron — entry/threshold
II8–15Tartarus Grid — industrial/ruin
III16–22Abyssal Styx — liquid/organic
IV23–30Phlegethon Wastes — radiation/chaos
V31–47Elysian Anomaly — surreal/divine
VI48–52Aquatic Layer — atmospheric haze
VII53–62Fog Realm — fog overlay
VIII63–70Molten Core — heat shimmer
IX71–79Crystal Wastes — prismatic refraction
X80–87Dark Sector — gravitational lensing
XI88+Hybrid Nexus — combination FX

Smooth-archetype filter criteria (used for in-mission backgrounds): nt === 0, warp ≤ 0.7, 0.4 ≤ density ≤ 0.65, sat ≤ 1.0, SURF_MODE[i] === 0.

Per-planet nebula archetypes

Planet IDArchetypeNotes
3Void Nebuladesaturated purple + bright orange
12Abandoned Station (index 15)luminosity × 1.5
21Dusk Haze (index 50)luminosity × 1.95
30Solaris Nebulaamber/orange
31Speedway Nebulaelectric blue
32Eden Nebuladeep green
33Old Earth Nebulagrey-green mist
34Network Station Nebulacyan
35Delphi Nebulaviolet crystal
36Desolation Nebularust-red
37Obelisk Nebuladeep indigo

Nebula archetype knobs

KnobRangeEffect
pa, pb, pc, pdvec3 eachcosine palette parameters
warp0–3domain warp strength (0 = flat, 3 = chaotic)
density0–1nebula opacity/fill
speedanimation/palette drift speed
thresh0–1visibility threshold (higher = more void)
bgvec3 0–1background base color
shapesq, dot, dia, tri, hex, star5, spark, crossnear-layer star shape
nf0.3–1.5noise frequency multiplier
nt0, 1, 2classic FBM, ridged, voronoi
ecvec3 0–255emission color for star glow
satdefault 1.0saturation multiplier
lumdefault 1.0luminosity multiplier
fg0–1fog overlay intensity

Boss-arena terrain patterns

Pattern IDPillarsHazard padsPlacement
open00
pillar_ring60ring at 65% arena radius
pillar_cross40cardinal points at 55% arena radius
hazard_pads04ring at 50% arena radius
corridor80two walls of 4 along long axis at 55% short-axis offset
gauntlet60two staggered rows of 3 along long axis at 40% short-axis offset

Pillar HP: 300. Pillar radius: 30 px. Hazard pad damage: 18 DPS. Hazard pad radius: 70 px.

How it works

World generation entry

WorldGenerator.generate(world, biomeId, levelData?) is the entry point. It clears world.terrain / structures / hubs / spokes, sets world.biomeId and world.levelRadius, then dispatches:

  • sunrise_city biome → _fillCityGrid (grid of city blocks separated by 200 px roads).
  • levelData supplied → zone-aware path. Hubs and spokes are copied from levelData.generation, then _fillTerrain runs with zone-grid filtering.
  • Otherwise → legacy ring-based path. _generateHubAndSpoke produces hubs in concentric rings, then _fillTerrain runs without zone-grid filtering.

If levelData is present, _validateGeneration warns when terrain bounding circles overlap hub zones.

Hub-spoke precompute (zone-aware)

precomputeWorld(config, spawnX, spawnY, gridRadius) calls generateHubsAndSpokes over a square chunk grid centered on spawn. Default grid radius: 10 chunks each direction (20×20 area). Steps:

  1. Compute chunk size from pattern + knobs via calcChunkSize.
  2. For each chunk, run pattern-specific hub generator (grid / zigzag / rings / chaotic) with a per-chunk Mulberry32 RNG seeded from globalSeed × 73856093 ^ cx × 19349663 ^ cy × 83492791.
  3. Append all hubs to a flat list, recording per-chunk indices.
  4. For each hub, spawn 1–2 warp puddles via spawnPuddlesForHub.
  5. Build spokes globally: grid pattern → orthogonal H/V only; chaotic / rings / zigzag → maximal planar graph (shortest-first, no edge crossings).
  6. Merge overlapping warp puddles into groups via union-find.

Hub size: lerp(hubRMin, hubRMax, hubSize). Hub radius is then jittered ±10%.

Four-phase terrain fill

Phase A (cluster fill) iterates a grid over either the zone-grid bounds (zone-aware) or ±levelRadius. Per cell, with probability 1 − clusterSkipRate:

  • Place a center piece (weighted-pick from mix.centers) at the cell’s center plus 50%-of-step jitter.
  • Place a ring of mediums (count scales with intensity) at distance centerR + 40..90.
  • Place a ring of smalls (count scales with intensity) at distance centerR + 100..180.

Each placement runs tryPlace: scan terrainList, reject if the new piece’s bounding circle plus OVERLAP_PAD overlaps an existing piece. OVERLAP_PAD interpolates from 75 px at intensity 0 to 0 px at intensity ≥ 0.83.

Zone-aware mode rejects pieces whose centers fall outside wilds in the zone grid (per-cell check using estimated piece radii of 150 / 100 / 60 px for center/medium/small).

Phase B carves hubs and spokes in legacy mode. A piece is dropped if its center is within hub.clearRadius + boundingRadius × 0.5 + 30 of any hub, or within spoke.width × 0.5 + boundingRadius × 0.3 + 20 of any spoke segment.

Phase C (border wall) is removed.

Phase D (floaters) is disabled. world.floaters is always [].

After all phases, deduplicateTerrain runs: for each piece, delete any later piece whose center is within 2 × pieceRadius. Buildings and pillars use boundingRadius × 0.30 instead of full bounding for this test. Then buildTerrainChunks indexes all terrain into 1024 px spatial chunks.

Sunrise City layout

City uses a regular grid: 1000 px blocks of buildings separated by 200 px roads. Block centers are placed at (g + 500, g + 500) where g steps by 1200. Within each block, a 2×2 sub-grid of building slots is filled with random shapes from the buildings pool (40% from centers, 60% from smalls). Buildings with centers landing in a road corridor (localX > 1000 || localY > 1000) are rejected.

After buildings are placed and deduplicated, perpendicular roads are registered as full-length spokes (200 px wide) so the event placer treats them like normal corridors. A single start hub is placed at origin with radius 300 and clearRadius 600.

Zone classification

classifyZone(px, py, hubs, spokes, spokeHalfWidth, illuminationMap):

  1. Check every hub: if (px − hub.x)² + (py − hub.y)² ≤ hub.r², return hub_lit (if illuminated) or hub_dark.
  2. Check every spoke: compute point-to-segment squared distance to the segment between the two endpoint hubs. If ≤ spokeHalfWidth², return spoke_lit (if either endpoint hub is illuminated) or spoke_dark.
  3. Otherwise return wilds.

The zone grid (buildZoneGrid) is precomputed once at level load: structural zone (hub vs spoke vs wilds) is baked per 16 px cell; illumination is resolved live from the illumination map at runtime. Out-of-bounds cells return wilds.

Event placement

placeEvents(world, eventPool) walks a 500 × 350 px grid across the playable area. Per cell, with probability 66% (34% skipped):

  1. Jitter cell center by ±40%. Reject if distance from origin > 95% of level radius or < 700 px.
  2. Reject unless in clear area: inside clearRadius × 0.7 of a hub, or within spokeWidth × 0.35 of a spoke center segment.
  3. Reject if edge distance to any existing event < 400 px.
  4. Pick event type uniformly from eventPool. Jitter event radius by ±20%.
  5. Delete every terrain piece within event.radius + 80 + terrain.boundingRadius of the event.

After grid placement, a cleanup pass removes any event whose circle plus 150 px pad intersects an earlier event.

Sub-events: for each non-start hub, with 45% probability spawn one additional event of a random type within ±100 px of the hub, then clear surrounding terrain. Each hub also seeds 20–50 crate slots in a 700 px ring (actual crate spawning is delegated to a pool system).

Dynamic expansion

Every frame, expandTerrain(world, playerX, playerY) runs:

  1. Compute the super-chunk bounds (800 px chunks) covering ±2200 px around the player.
  2. For each not-yet-generated super-chunk, mark generated, then test whether the chunk center falls within any hub clear zone, spoke corridor, or event clear zone (each with super-chunk × 0.7 or × 0.5 pad). Skip if so.
  3. With 97% probability, generate one cluster (center + 3–5 mediums + 4–8 smalls) at the chunk center with ±40% jitter. Reject pieces overlapping existing terrain (using AABB pre-filter then radial test with 20 px pad).
  4. If any terrain was added, run a separate per-chunk RNG to roll 2–4 crates with 85% chance per chunk.

After expansion, terrain garbage-collection deletes any piece beyond 3000 px from the player. Deleted pieces have their super-chunk key removed from _generatedChunks so re-entry regenerates them. Floaters and crates beyond the same distance are also culled (floaters always empty in current generation).

If terrain was added or removed, the world runs deduplicateTerrain and buildTerrainChunks again.

Run XP curve

LevelingSystem.update(game) loops checkLevelUp until current XP is below the next threshold. _ensureThreshold(level) returns the cumulative XP needed; the table extends on demand beyond level 20 by appending prev + lastCost × 1.12 rounded up. There is no level cap. Each level-up increments enemyDifficultyLevel, fires the level_up signal, and pushes a level_up entry onto rewardQueue.

Reward generation

generateRewardChoices(game, count, ship) builds three pools:

  • New modifiers: modifier IDs not yet owned, gated by game.runDef.context.upgradePool if present. damage_* modifiers require an owned weapon with the matching damage tag (or damage_all requires at least one weapon).
  • Upgrade modifiers: owned modifiers below maxLevel.
  • Weapon upgrades: owned weapons below level 20. Merge candidates: pairs of level-20 non-legendaries that can fuse into a legendary.

Merge cards are slotted first (cap ceil(count/2)). Remaining slots roll independently: 35% chance to draw from the weapon-upgrade pool, 65% from the modifier pool. If a pool is empty, the other is used. Every chosen card rolls its own rarity using the rarity roll table, and rarity drives rarityMult for effect scaling.

generateWeaponChoices(game, ship, count) filters all weapons (excluding disabled, legendary, owned, banished, and out-of-pool) and returns up to count non-duplicate picks at uniform weight.

Reward application

applyReward(choice, ship, game):

  • weapon — push a new weapon entry with cooldown derived from def.fireRate.base, random fire-timer offset, level 1.
  • weapon_upgrade — add max(1, round(rarityMult)) levels, capped at 20. If sympathetic_resonance artifact is owned, cascade the same number of bonus levels to all other weapons.
  • weapon_merge — consume the two parent weapons and grant the resulting legendary via mergeWeapons.
  • modifier — increment game.upgradeCounts[id], then register every effect as a permanent Modifier with source level_up:<modId>:<stat>. Recalc settles stats; HP/Shield current values bump by the new-max delta.
  • artifactgrantArtifact (new or level up).
  • shooting_star — dispatch by category: bump all weapons, all mods, all artifacts, lowest weapon +3, apply starpower state for 60 s, or grant +1 reroll/banish/refuel.

Per-planet track XP

getXpProgress(planetId, xp) returns {level, currentXp, nextThreshold, pct, totalXp}. Players always start at level 1; thresholds gate levels 2 through 10. At level 10 the return is {level: 10, currentXp: 0, nextThreshold: 0, pct: 1, totalXp: xp}. getOverallPct reports cumulative progress across all 10 levels.

Interactions

  • Run loads with a RunDefinition carrying context.planetId. The PlanetDef resolves biome, enemy set, boss, fog alpha, post-processing mode, and level preset.
  • levelPreset resolves to a LevelConfig consumed by generateHubsAndSpokes and the zone-aware terrain fill.
  • world.events populates after generation; runs without events spawn only crates.
  • Reward cards push onto game.rewardQueue. The HUD pops and presents cards via generateRewardChoices.
  • Modifier registration writes to Modifiers.add keyed by level_up:<modId>:<stat>. Modifiers.recalc resets stats to ship._base and re-applies all registered modifiers; level-up picks survive artifact-modifier expiry cycles.
  • Weapon-chest pickups call resolveWeaponChestUpgrade(currentLevel, chestRarity) and bypass the level-up reward flow entirely. Weapon level-ups appear on level-up rewards only for owned weapons.
  • Background visuals call PLANET_ARCHETYPE_IDX[planetId] for in-run rendering. Hub screen calls PLANET_ARCHETYPES[planetId].
  • Boss arenas overlay one of six terrain patterns from TERRAIN_PATTERNS independent of biome terrain.
  • The XP track awards XP from challenge completions and run completions; XP gates 10 per-planet reward levels.

What it does NOT do

  • Does not enforce a level cap on player XP — thresholds extend infinitely past level 20.
  • Does not place border walls — the world is infinite; there is no level boundary.
  • Does not spawn free-floating physics asteroids — world.floaters is always empty.
  • Does not bleed damage from shield to hull — the world system does not handle damage routing.
  • Does not award weapon level-ups from level-up cards for unowned weapons — weapon-chest pickups are the only source for new weapons.
  • Does not handle interactive crates inside the world generator — crate lifecycle is delegated to a separate pool.
  • Does not let the zone-aware fill carve hubs/spokes after placement — zone-aware terrain is rejected pre-placement against the zone grid.
  • Does not render planet 99 (Obsidian Spire) in the hub swiper — it is data-only.
  • Does not show legendary weapons in weapon-chest pulls — legendaries are merge-only.
  • Does not banish charge-grant shooting-star cards (grant_reroll, grant_banish, grant_refuel).
  • Does not place sub-events on the start hub or hubs flagged as objectives.