Skip to content

Terrain system

Spawn and manage terrain objects: nebulae, asteroids, black holes, and anomalies.

Overview

Terrain objects are passive space objects that affect gameplay through their physical presence (collision, visual obstruction) or special engine behaviours (gravity wells, nebula interference). They are spawned with art IDs from the terrain catalogue and are otherwise treated like any other space object for targeting and role-based querying.

Use terrain_spawn as the general-purpose call. Convenience wrappers exist for common terrain types. All terrain spawners return an Agent object whose ID can be stored for later deletion with delete_object.

Quick example

== place_terrain ==
nebula = terrain_spawn("nebula2", 1000, 0, 2000)
asteroid = terrain_spawn("asteroid", 3000, 0, 1000)
add_role(nebula, "safe_zone")
from sbs_utils.procedural.terrain import terrain_spawn
from sbs_utils.procedural.space_objects import delete_object

nebula = terrain_spawn("nebula2", 1000, 0, 2000)
asteroid = terrain_spawn("asteroid", 3000, 0, 1000)

# Remove when no longer needed
delete_object(nebula)

API

terrain_asteroid_clusters(terrain_value, center=None, selectable=False, points=None)

Scatter random asteroid clusters across the map.

Parameters:

Name Type Description Default
terrain_value int

0–4 scale controlling cluster count and density.

required
center Vec3

Map centre. Defaults to (0, 0, 0).

None
selectable bool

Make asteroids selectable on 2D radar. Defaults to False.

False
points list[Vec3]

Explicit cluster origins instead of random positions. Defaults to None.

None

Returns:

Type Description

list[Vec3]: The cluster centre positions used.

terrain_field_plan_keyed(key, cell, x_min, z_min, x_max, z_max, nebula_chance, asteroid_chance, exclude=None, exclude_radius=0, y_min=-375, y_max=375)

Decide what the keyed terrain field contains, without spawning.

Walks the global lattice (scatter.grid_keyed) and, per cell, uses scatter.cell_roll to pick nebula / asteroid / empty. Pure and deterministic: the same (key, cell) yields the same plan for any cell, so a smaller region is the centered subset of a larger one. exclude points (e.g. stations) within exclude_radius are skipped.

Parameters:

Name Type Description Default
key int

World/map seed (concrete; caller maps "random" to a random int).

required
cell float

Lattice cell size.

required
x_min, z_min, x_max, z_max float

World bounds.

required
nebula_chance, asteroid_chance float

Per-cell probabilities (0..1).

required
exclude list

Points/objects to keep clear of.

None
exclude_radius float

Clearance radius around exclude.

0
y_min, y_max float

Height range for the points.

required

Returns:

Type Description

list[tuple[Vec3, str]]: (position, "nebula"|"asteroid") entries.

terrain_random_point_box(all_points, left, top, front, right, bottom, back, inside=True, count=1)

wraps a set of points in a generator returning unique points inside (or outside) a box

Parameters:

Name Type Description Default
all_points _type_

The source set of points

required
left _type_

left (x)

required
top _type_

top (y)

required
front _type_

front (z)

required
right _type_

right (x)

required
bottom _type_

bottom (y)

required
back _type_

back (z)

required
inside bool

Within the box or out side it. Defaults to True.

True
count int

Number of points each iteration. Defaults to True.

1

Yields:

Type Description

Vec3 | list[Vec3]: A random point

terrain_remove_points_near(all_points, test_points, radius)

Return only the points that are outside a given radius of every test point.

Filters all_points by removing any point within radius of at least one entry in test_points. Useful for clearing spawn candidates around existing objects.

Parameters:

Name Type Description Default
all_points list[Vec3]

Candidate spawn positions.

required
test_points list[Vec3 | Agent | int]

Exclusion reference points. Non-Vec3 items are resolved to their space-object position.

required
radius float

Exclusion radius in simulation units.

required

Returns:

Type Description

list[Vec3]: Subset of all_points farther than radius from every test point.

terrain_set_nebula_object_size(size=2500)

OPT-IN (experimental): raise the per-object nebula size so clusters spawn FEWER, BIGGER objects.

A cluster's object count is cluster_size // NEB_SIZE_LARGE, so raising this makes the existing generator produce fewer/bigger objects (a 10000 cluster: ~36 -> ~16 at 2500 = ~55% less overdraw) while keeping all its scatter/drift/ jitter - no re-tuning. Default behavior is UNCHANGED unless a mission calls this.

Pairs with the projection depth shader fix (NEB_DEPTH_PROJECTION in shader-emissivenebula.ps): sizes above ~3000 need it to avoid the "sphere at origin" artifact. 2500 is clean even without it. Call once before spawning nebulae; pass 1500 to restore the default.

terrain_setup_nebula(nebula, diameter=4000, density_coef=1.0, color='yellow', seed=None)

Apply visual and physical properties to an existing nebula space object.

Parameters:

Name Type Description Default
nebula SpaceObject | Agent

The nebula to configure.

required
diameter int

Nebula diameter (capped at NEB_MAX_SIZE). Defaults to 4000.

4000
density_coef float

Visual nebula density multiplier (3D view). Defaults to 1.0.

1.0
color str | dict

Colour name or a full colour dict. Defaults to "yellow".

'yellow'
seed int

The shader's random_seed. Defaults to None -- drawn here, as always. The sower passes one in because it draws every per-object value up front, so a queued chunk contains no randomness.

None

terrain_sow_begin(over=6, focus=None)

Start sowing: terrain fill queued and spread over over sim-seconds.

What this guarantees: each queued call creates exactly what it would have created inline -- it carries the RNG state it was queued under, so deferring a cluster never changes that cluster.

NEBULA is identical either way, cluster for cluster: its whole plan (colour, height, size, shader seed per object) is drawn at queue time in the same order the inline spawn draws it, so the caller's stream advances exactly as it would have. The same is true of terrain_spawn_field_keyed, which already isolates its RNG per cell.

ASTEROID clusters do shift the stream: their spawn loop's randomness is interleaved with reads of the spawned object (a rock's exclusion radius sets where its satellites go), so it cannot be drawn up front the same way. Deferring it moves that consumption, which changes what a LATER cluster draws. Cluster centres come from one up-front scatter, so the macro layout is unchanged; what differs is rock counts and scales per cluster. Across seeds this is a different sample of the same distribution, not a thinner field (measured: +2% net, both directions). The result is fully deterministic -- the same seed sows the same field every time -- just not the same field as not sowing. Since sowing is opt-in per map, a map is only ever played one way.

Parameters:

Name Type Description Default
over float

Seconds to spread the work across. Defaults to 6.

6
focus Vec3 | tuple

Work runs nearest-first from here, so the space around it is correct first. Defaults to the map origin.

None

Returns:

Name Type Description
DripQueue

the queue, for callers that want to inspect it.

terrain_sow_complete()

Promise that resolves once the sown terrain has all been created.

Example

terrain_sow_begin(over=6) terrain_asteroid_clusters(terrain_value) await terrain_sow_complete()

terrain_sow_end()

Stop queueing; terrain_* calls spawn inline again. Anything already queued keeps draining -- use terrain_sow_flush() to force it out now.

terrain_sow_flush()

Run all queued terrain work immediately. Returns how many units ran.

For code that must see the whole field right now -- a test, a headless conformance run, or a query that cannot wait for the drip.

terrain_sow_pending()

How many queued units of terrain work are still to run.

terrain_sow_reset()

Drop queued work and leave sowing off (mission reset).

terrain_spawn_asteroid_box(x, y, z, size_x=10000, size_z=None, density_scale=1.0, density=1, height=1000, selectable=False, is_tiled=False)

Spawn asteroids scattered inside a box volume; density is per 1000 units.

Parameters:

Name Type Description Default
x float

Box origin X.

required
y float

Box origin Y (unused for placement; passed through).

required
z float

Box origin Z.

required
size_x int

Box width along X. Defaults to 10000.

10000
size_z int | None

Box depth along Z. Defaults to size_x.

None
density_scale float

Multiplier for asteroid count. Defaults to 1.0.

1.0
density int

Base density per 1000 units. Defaults to 1.

1
height int

Box height (Y spread). Defaults to 1000.

1000
selectable bool

Make asteroids selectable on 2D radar. Defaults to False.

False
is_tiled bool

Adjust origin for map-editor tile coordinates. Defaults to False.

False

terrain_spawn_asteroid_points(x, y, z, points, radius=10000, density_scale=1.0, density=1, height=1000, selectable=False)

Spawn asteroids along a polyline defined by a list of 2D points.

Offsets each point by (x, z) and scatters asteroids along the resulting line segments.

Parameters:

Name Type Description Default
x float

X offset applied to all points.

required
y float

Unused.

required
z float

Z offset applied to all points.

required
points list[tuple]

2D (x, z) vertices defining the polyline.

required
radius int

Unused. Defaults to 10000.

10000
density_scale float

Multiplier for per-segment density. Defaults to 1.0.

1.0
density int

Base density. Defaults to 1.

1
height int

Y spread of each asteroid. Defaults to 1000.

1000
selectable bool

Make asteroids selectable on 2D radar. Defaults to False.

False

terrain_spawn_asteroid_scatter(cluster_spawn_points, height, selectable=False)

Spawn a randomised asteroid (with possible satellite cluster) at each given point.

Every asteroid path in this module funnels through here, so this is where sowing intercepts: inside a sow scope the whole cluster is queued as one unit of work and created later, identically. See terrain_sow_begin.

Parameters:

Name Type Description Default
cluster_spawn_points Iterable[Vec3]

Spawn positions.

required
height int

Controls Y scatter range around each point.

required
selectable bool

Make asteroids selectable on 2D radar. Defaults to False.

False

terrain_spawn_asteroid_sphere(x, y, z, radius=10000, density_scale=1.0, density=1, height=1000, selectable=False)

Spawn asteroids scattered inside a sphere volume; density is per 1000 units.

Parameters:

Name Type Description Default
x float

Sphere centre X.

required
y float

Sphere centre Y.

required
z float

Sphere centre Z.

required
radius int

Sphere radius. Defaults to 10000.

10000
density_scale float

Multiplier for asteroid count. Defaults to 1.0.

1.0
density int

Base density per 1000 units. Defaults to 1.

1
height int

Y spread of each asteroid. Defaults to 1000.

1000
selectable bool

Make asteroids selectable on 2D radar. Defaults to False.

False

terrain_spawn_black_hole(x, y, z, gravity_radius=1500, gravity_strength=1.0, turbulence_strength=1.0, collision_damage=200)

Spawn a black hole (maelstrom) terrain object at the given position.

Parameters:

Name Type Description Default
x float

X position.

required
y float

Y position.

required
z float

Z position.

required
gravity_radius int

Radius within which objects are pulled in. Defaults to 1500.

1500
gravity_strength float

Pull speed multiplier. Defaults to 1.0.

1.0
turbulence_strength float

Turbulence intensity. Defaults to 1.0.

1.0
collision_damage int

Damage on entry into the event horizon. Defaults to 200.

200

Returns:

Name Type Description
SpaceObject

The spawned black hole object.

terrain_spawn_black_holes(lethal_value, center=None, points=None)

Spawn multiple black holes based on the game's lethal terrain value.

Parameters:

Name Type Description Default
lethal_value int

Number of black holes to spawn.

required
center Vec3

Map centre. Defaults to (0, 0, 0).

None
points list[Vec3]

Explicit spawn positions. Defaults to None (random within 75 000 units of centre).

None

Returns:

Type Description

list[SpaceObject]: The spawned black hole objects.

terrain_spawn_field_keyed(key, cell, x_min, z_min, x_max, z_max, terrain_value, nebula_chance, asteroid_chance, y_min=-375, y_max=375, exclude=None, exclude_radius=0, selectable=False, marker=True)

Spawn a position-keyed asteroid / nebula field over the given bounds.

The field is a pure function of (key, position): the same seed produces the same field, and a small map is the centered subset of a large one. Each cluster's contents come from a per-cell RNG (the global RNG is re-seeded per cell from the cell's coords and restored afterward), so the existing cluster-spawn code is reused and stays deterministic. terrain_value (0-4) controls cluster richness; nebula_chance / asteroid_chance are per-cell probabilities.

Returns:

Type Description

list[tuple[Vec3, str]]: the plan that was spawned.

terrain_spawn_monsters(monster_value, center=None, points=None)

Spawn monster-bestiary prefabs based on the monster difficulty value.

Each monster is rolled from monster_species_weights (Typhon-dominant), so the field is a mix of hostile, tame and helpful creatures. Reassign that list to change the mix.

Parameters:

Name Type Description Default
monster_value int

Number of monsters to spawn.

required
center Vec3

Map centre. Defaults to (0, 0, 0).

None
points list[Vec3]

Explicit spawn positions. Defaults to None (random within 75 000 units of centre).

None

Returns:

Type Description

list[Vec3]: The spawn positions used.

terrain_spawn_nebula_box(x, y, z, size_x=10000, size_z=None, density_scale=1.0, density=1, height=1000, cluster_color=None, selectable=False, marker=True, name='')

Spawn nebulae scattered inside a box volume.

Delegates to terrain_spawn_nebula_common with box geometry.

Parameters:

Name Type Description Default
x float

Box origin X.

required
y float

Unused.

required
z float

Box origin Z.

required
size_x int

Box width along X. Defaults to 10000.

10000
size_z int | None

Box depth; defaults to size_x.

None
density_scale float

Nebula count multiplier. Defaults to 1.0.

1.0
density int

Visual density per nebula (3D view). Defaults to 1.

1
height int

Y spread. Defaults to 1000.

1000
cluster_color str | int | dict | None

Colour override; see terrain_spawn_nebula_common. Defaults to None (random).

None
selectable bool

Make nebulae selectable. Defaults to False.

False
marker bool

Place a radar marker. Defaults to True.

True
name str

Marker name. Defaults to "".

''

Returns:

Type Description

list[SpaceObject]: Spawned nebula objects.

terrain_spawn_nebula_clusters(terrain_value, center=None, selectable=False, points=None, marker=True, name='')

Scatter random nebula clusters across the map and merge nearby markers.

After spawning, neighbouring nebula_marker objects within 15 000 units are merged into a single marker that represents the combined cluster.

Parameters:

Name Type Description Default
terrain_value int

0–4 scale controlling cluster count and density.

required
center Vec3

Map centre. Defaults to (0, 0, 0).

None
selectable bool

Make nebulae selectable on 2D radar. Defaults to False.

False
points list[Vec3]

Explicit cluster origins. Defaults to None (random positions).

None
marker bool

Place a radar marker at each cluster origin. Defaults to True.

True
name str

Name assigned to each radar marker. Defaults to "".

''

Returns:

Type Description

list[SpaceObject]: All spawned nebula objects.

terrain_spawn_nebula_common(x, y, z, size_x=10000, size_z=None, radius=None, density_scale=1.0, density=1, height=1000, cluster_color=None, selectable=False, marker=True, name='')

Spawn a nebula cluster using either box or sphere geometry.

Shared implementation called by terrain_spawn_nebula_box and terrain_spawn_nebula_sphere. Distributes nebulae with noise-based scatter and optionally places a radar marker at the cluster centre.

Parameters:

Name Type Description Default
x float

Centre X position.

required
y float

Centre Y position.

required
z float

Centre Z position.

required
size_x int

Box width or sphere radius X. Defaults to 10000.

10000
size_z int | None

Box depth; if None uses size_x. Defaults to None.

None
radius float | None

Override for sphere scatter radius. Defaults to None (uses box geometry).

None
density_scale float

Multiplier for cluster density. Defaults to 1.0.

1.0
density float

Visual density of each nebula (3D view). Defaults to 1.

1
height int

Vertical spread in simulation units. Defaults to 1000.

1000
cluster_color str | int | dict | None

Nebula colour — a colour name string (e.g. "purple"), an integer index into the colour table, a full colour dict, or None for a random colour. Defaults to None.

None
selectable bool

Make nebulae selectable on 2D radar. Defaults to False.

False
marker bool

Place a radar marker at the cluster origin. Defaults to True.

True
name str

Name assigned to the radar marker. Defaults to "".

''

Returns:

Type Description

list[SpaceObject]: Spawned nebula objects.

terrain_spawn_nebula_scatter(cluster_spawn_points, height, cluster_color=None, diameter=NEB_MAX_SIZE, density=1.0, selectable=False)

Spawn a nebula at each given point with randomised Y scatter.

Parameters:

Name Type Description Default
cluster_spawn_points Iterable[Vec3]

Spawn positions.

required
height int

Controls Y scatter range around each point.

required
cluster_color str | int | dict | None

Colour name, index (0=purple, 1=red, 2=blue, 3=yellow), dict, or None for random. Defaults to None.

None
diameter int

Max nebula diameter. Defaults to NEB_MAX_SIZE.

NEB_MAX_SIZE
density float

Visual nebula density (3D view). Defaults to 1.0.

1.0
selectable bool

Make nebulae selectable on 2D radar. Defaults to False.

False

Returns:

Type Description

list[SpaceObject]: The spawned nebula objects.

terrain_spawn_nebula_sphere(x, y, z, radius=NEB_MAX_SIZE, density_scale=1.0, density=1.0, height=1000, cluster_color=None, selectable=False, marker=True, name='')

Spawn nebulae scattered inside a sphere volume.

Delegates to terrain_spawn_nebula_common with sphere geometry.

Parameters:

Name Type Description Default
x float

Sphere centre X.

required
y float

Sphere centre Y.

required
z float

Sphere centre Z.

required
radius int

Sphere radius. Defaults to NEB_MAX_SIZE.

NEB_MAX_SIZE
density_scale float

Nebula count multiplier. Defaults to 1.0.

1.0
density float

Visual density per nebula. Defaults to 1.0.

1.0
height int

Y spread. Defaults to 1000.

1000
cluster_color str | int | dict | None

Colour override; see terrain_spawn_nebula_common. Defaults to None (random).

None
selectable bool

Make nebulae selectable. Defaults to False.

False
marker bool

Place a radar marker. Defaults to True.

True
name str

Marker name. Defaults to "".

''

Returns:

Type Description

list[SpaceObject]: Spawned nebula objects.

terrain_spawn_stations(DIFFICULTY, lethal_value, x_min=-32500, x_max=32500, center=None, min_num=0, points=None)

Spawn starbases weighted by difficulty and optionally surround them with minefields.

Parameters:

Name Type Description Default
DIFFICULTY int

Game difficulty (affects station count and type mix).

required
lethal_value int

Lethal terrain level; > 0 wraps minefields around each station.

required
x_min int

Minimum X spawn bound. Defaults to -32500.

-32500
x_max int

Maximum X spawn bound. Defaults to 32500.

32500
center Vec3

Map centre. Defaults to (0, 0, 0).

None
min_num int

Minimum station count. Defaults to 0.

0
points list[Vec3]

Explicit spawn positions. If provided, stations are sampled from this list. Defaults to None.

None

Returns:

Type Description

list[SpaceObject]: The spawned station objects.

terrain_to_value(dropdown_select, default=0)

Convert a terrain density string to an integer level (0–4).

Parameters:

Name Type Description Default
dropdown_select str

Density string: "few" → 1, "some" → 2, "lots" → 3, "max"/"many" → 4.

required
default int

Value returned for unrecognised strings. Defaults to 0.

0

Returns:

Name Type Description
int

Terrain density level 0–4.