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 |
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 |
0
|
y_min, y_max
|
float
|
Height range for the points. |
required |
Returns:
| Type | Description |
|---|---|
|
list[tuple[Vec3, str]]: |
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- |
required |
radius
|
float
|
Exclusion radius in simulation units. |
required |
Returns:
| Type | Description |
|---|---|
|
list[Vec3]: Subset of |
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 |
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'
|
seed
|
int
|
The shader's |
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
|
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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. |
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
|
diameter
|
int
|
Max nebula diameter. Defaults to
|
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
|
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 |
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; |
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 |
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: |
required |
default
|
int
|
Value returned for unrecognised strings. Defaults to 0. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
Terrain density level 0–4. |