Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
class_name WorldGen
|
|
|
|
|
extends RefCounted
|
|
|
|
|
|
|
|
|
|
# Deterministic procedural world generation: the world is divided into
|
|
|
|
|
# square chunks, and each chunk's biome is derived from a seeded
|
|
|
|
|
# low-frequency noise field sampled at the chunk's own coordinates - so
|
|
|
|
|
# revisiting the same chunk always yields the same biome, and biomes form
|
|
|
|
|
# smooth multi-chunk regions instead of a per-chunk random checkerboard.
|
|
|
|
|
|
|
|
|
|
enum Biome { SPACE, ASTEROID_FIELD }
|
|
|
|
|
|
|
|
|
|
const CHUNK_SIZE: float = 1200.0
|
|
|
|
|
const NOISE_SEED: int = 1337
|
|
|
|
|
const ASTEROID_FIELD_THRESHOLD: float = 0.15
|
|
|
|
|
const PLANET_THRESHOLD: float = 0.33
|
2026-08-13 22:04:21 +02:00
|
|
|
const GAS_GIANT_THRESHOLD: float = 0.55
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
const PLANET_NAMES: Array = [
|
|
|
|
|
"Kepler's Rest", "Ceres Minor", "Vesta's Shadow", "Ironhold",
|
|
|
|
|
"Drift Anchor", "Stonefall", "Grey Watch", "Silent Quarry",
|
|
|
|
|
"Baros Rock", "Umbra Point",
|
|
|
|
|
]
|
2026-08-13 21:07:35 +02:00
|
|
|
const GAS_GIANT_NAMES: Array = ["Aurelia", "Boreas", "Cerulean Deep", "Helios Major", "Vesper"]
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
static var _noise: FastNoiseLite
|
|
|
|
|
static var _planet_noise: FastNoiseLite
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func _get_noise() -> FastNoiseLite:
|
|
|
|
|
if _noise == null:
|
|
|
|
|
_noise = FastNoiseLite.new()
|
|
|
|
|
_noise.seed = NOISE_SEED
|
|
|
|
|
_noise.frequency = 0.06
|
|
|
|
|
return _noise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func _get_planet_noise() -> FastNoiseLite:
|
|
|
|
|
if _planet_noise == null:
|
|
|
|
|
_planet_noise = FastNoiseLite.new()
|
|
|
|
|
_planet_noise.seed = NOISE_SEED + 777
|
|
|
|
|
# Higher frequency than the biome noise so it spikes on individual
|
|
|
|
|
# chunks rather than forming its own smooth multi-chunk regions -
|
|
|
|
|
# "once or twice per asteroid biome [region]" needs a sparse, mostly
|
|
|
|
|
# independent chance per chunk, not another smooth blob.
|
|
|
|
|
_planet_noise.frequency = 0.35
|
|
|
|
|
return _planet_noise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func chunk_coords(world_pos: Vector2) -> Vector2i:
|
|
|
|
|
return Vector2i(int(floor(world_pos.x / CHUNK_SIZE)), int(floor(world_pos.y / CHUNK_SIZE)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func chunk_origin(chunk: Vector2i) -> Vector2:
|
|
|
|
|
return Vector2(chunk.x * CHUNK_SIZE, chunk.y * CHUNK_SIZE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func biome_at_chunk(chunk: Vector2i) -> int:
|
|
|
|
|
var n: float = _get_noise().get_noise_2d(float(chunk.x), float(chunk.y))
|
|
|
|
|
if n > ASTEROID_FIELD_THRESHOLD:
|
|
|
|
|
return Biome.ASTEROID_FIELD
|
|
|
|
|
return Biome.SPACE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func has_planet_at_chunk(chunk: Vector2i) -> bool:
|
|
|
|
|
var n: float = _get_planet_noise().get_noise_2d(float(chunk.x), float(chunk.y))
|
2026-08-13 22:04:21 +02:00
|
|
|
return (biome_at_chunk(chunk) == Biome.ASTEROID_FIELD and n > PLANET_THRESHOLD) or (biome_at_chunk(chunk) == Biome.SPACE and n > GAS_GIANT_THRESHOLD)
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
static func planet_data_at_chunk(chunk: Vector2i) -> Dictionary:
|
|
|
|
|
# Single source of truth for a planet's identity within its chunk -
|
|
|
|
|
# seeded purely from the chunk's own coordinates (same pattern as the
|
|
|
|
|
# biome/planet noise fields above) so it's reproducible from anywhere,
|
|
|
|
|
# without needing an actual spawned Planet node. Both PlanetField (to
|
|
|
|
|
# actually spawn the planet) and the world map (to draw a marker for
|
|
|
|
|
# an explored-but-not-currently-loaded chunk) call this, so the two
|
|
|
|
|
# can never drift out of sync with each other.
|
|
|
|
|
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
|
|
|
|
rng.seed = hash(chunk)
|
|
|
|
|
var local_pos: Vector2 = Vector2(rng.randf() * CHUNK_SIZE, rng.randf() * CHUNK_SIZE)
|
|
|
|
|
var planet_name: String = PLANET_NAMES[rng.randi() % PLANET_NAMES.size()]
|
|
|
|
|
var shape_seed: int = rng.randi()
|
2026-08-13 21:07:35 +02:00
|
|
|
# A stable per-chunk roll makes gas giants persistent landmarks rather
|
|
|
|
|
# than a different resource whenever the scene reloads.
|
2026-08-13 22:04:21 +02:00
|
|
|
var gas_giant: bool = biome_at_chunk(chunk) == Biome.SPACE
|
2026-08-13 21:07:35 +02:00
|
|
|
if gas_giant:
|
|
|
|
|
planet_name = GAS_GIANT_NAMES[rng.randi() % GAS_GIANT_NAMES.size()]
|
|
|
|
|
return {"local_pos": local_pos, "name": planet_name, "shape_seed": shape_seed,
|
|
|
|
|
"planet_type": "gas_giant" if gas_giant else "rocky", "resource_type": "gas" if gas_giant else "ore"}
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
static func biome_color(biome: int) -> Color:
|
|
|
|
|
match biome:
|
|
|
|
|
Biome.ASTEROID_FIELD:
|
|
|
|
|
return Color(0.55, 0.36, 0.18, 1.0)
|
|
|
|
|
_:
|
|
|
|
|
return Color(0.10, 0.13, 0.28, 1.0)
|