Skip to content

Mount

Weld an object into another object's body frame and let the engine hold it there - a weapon turret on a hull, a sensor pod, a parasite craft. Pair with turret and you have an autonomous weapon mount: turret decides what to shoot, mount decides where it rides.

Engine-measured (1.3.5): sim.AddTractorConnection(host, mount, vec3(0,0,200), 0) held a mount at exactly 200.0u and exactly 0.0 deg off the host's nose while the host's heading swung 51 deg - the separation vector rotated with the hull. So the engine does the work every frame: no per-tick reposition, no tick task, and no frame lag.

Do not infer this from grav_tether's docs

grav_tether describes the offset point as world-fixed, which is true only of the case it uses - it never passes an offset, so its load reels to the source's own position. The two modules want opposite things: grav_tether drags a load behind a ship, mount bolts one onto it.

Lifecycle

+z local is forward, +x right, +y up. A mount spawns at the host's position and the weld pulls it into place.

  • A host killed in combat takes its mounts with it, honoring each mount's delete_with_host - pass False for a blown-off turret that survives as salvage.
  • A host removed by a script fires no destroy event, so mount_host_of() reads None for a vanished host and mount_prune_orphans() cleans up that path.
  • There is no module-level registry: the engine owns the connection, the host/mount relationship is an Agent link, and per-mount settings live in the mount's inventory - so nothing here can outlive its objects, and nothing needs a reset-ledger entry.

API

Mounts: weld an object into another object's BODY frame, and let the engine hold it.

A mount is any space object rigidly attached to a host at a fixed offset in the host's own frame - a weapon turret on a hull, a sensor pod, a parasite craft. Pair it with :mod:sbs_utils.procedural.turret and you have an autonomous weapon mount: the turret half decides what to shoot, this half decides where it rides. Neither knows about the other, which is why a tower, a station bolt-on and a ship mount are all the same code.

ENGINE-MEASURED (1.3.5, LM_TestRange/maps/test_tractor_mount.mast): the raw tractor API holds the target in the source's BODY frame::

sim.AddTractorConnection(host, mount, sbs.vec3(0, 0, 200), 0)

held a mount at exactly 200.0u and exactly 0.0 deg off the host's nose while the host's heading swung 51 deg - the separation vector rotated with the hull, (0,0,200) -> (155.5, 0, 125.8). So the ENGINE does the work every frame: no per-tick reposition, no tick task, and none of the one-frame lag a script-side transform suffers.

Do not infer this from grav_tether's prose. It called the offset point "world-fixed", which was only ever true of the case that module uses - a tow passes no offset, so its load reels to the host's own position. (grav_tether_attach's own parameter doc said "point (relative to source)" all along and was right; the surrounding prose was not.)

Why this does not just wrap grav_tether_lock. It could: that function passes an offset straight through with stiffness 0, which is the same weld. But grav_tether runs _enforce_impulse over every live tether, capping the SOURCE ship back to impulse - so a ship carrying bolted turrets could never warp. A mount is part of the ship; a tether is a thing the ship is dragging. Same engine call, opposite intent, and they must not share a registry: a global ClearTractorConnections silently unwelded every mount until grav_tether_clear_all was made to delete only its own connections.

No module-level state, deliberately. The engine owns the connection; the host->mount relationship is an Agent LINK and the per-mount settings live in the mount's own inventory. Agent._remove purges both on delete, so nothing here can outlive its objects and nothing needs a register_reset_state entry.

Every module-level function is prefixed, private ones included: MAST imports a module's functions into one flat, mission-wide namespace with no underscore filtering, so a helper named _key would turn any script's _key = ... into a compile error that empties the whole story.

mount_attach(host, mount, offset=None, delete_with_host=True)

Weld an existing object into the host's body frame.

Parameters:

Name Type Description Default
host Agent | int

The object to ride on.

required
mount Agent | int

The object to attach.

required
offset Vec3 | tuple

Position in the HOST's own frame - +z forward, +x right, +y up. Defaults to the host's center.

None
delete_with_host bool

Delete this mount when the host is destroyed (default). Pass False to leave it floating as debris - a blown-off turret that can be salvaged.

True

Returns:

Type Description

int | None: The mount's id, or None if either object is missing or the engine refused the connection.

mount_clear_all()

Release every mount without deleting anything.

There is no module-level registry to clear - the relationships live on the agents themselves and are purged with them - so this is for tests, for a mid-mission clean slate, and for reset_mission_state to drop the ENGINE-side welds deliberately.

Tolerates having no frame context: a reset can fire with none, and dropping our own state must never depend on the engine being there.

mount_count()

How many welded mounts exist. Cheap probe for tests and diagnostics.

mount_detach(host, mount, delete=False)

Release a mount. Optionally delete it.

Deletion goes through the procedural delete_object (deferred) rather than the engine call, which frees the C++ object synchronously and would leave anything still holding it pointing at freed memory.

mount_detach_all(host, delete=None)

Release every mount on a host.

Parameters:

Name Type Description Default
delete bool

Force-delete (True) or force-keep (False) every mount. Defaults to None, meaning honor each mount's own delete_with_host setting - which is what the host-destroyed path wants.

None

Returns:

Type Description

list[int]: The mounts released.

mount_host_of(mount)

The host a mount rides on, or None.

A host that no longer exists reads as None rather than a dangling id. The destroy dispatch cleans up ships killed in COMBAT, but a script can also just delete a ship outright, and that path fires no destroy event - so "my host is gone" has to be answerable from the link alone.

mount_is(obj)

Whether an object is currently mounted on something.

mount_list(host)

Every mount currently welded to a host, as a list of ids.

mount_offset(host, mount)

The body-frame offset a mount was welded at, as a Vec3.

mount_prune_orphans(delete=None)

Release mounts whose host is gone, honoring each one's delete_with_host.

The destroy dispatch covers a host killed in combat. This covers the other way a host vanishes - a script deleting it - which fires no destroy event and would otherwise leave armed objects welded to nothing.

Returns:

Type Description

list[int]: The orphans dealt with.

mount_ring(host, ship_key, count, radius=None, y=0.0, **kwargs)

Spawn count mounts evenly spaced on a ring in the host's body XZ plane.

The common case for bolting turrets onto a hull or a station. Because the offsets are body-frame, a station host and a maneuvering ship host behave identically.

Parameters:

Name Type Description Default
radius float

Ring radius. Defaults to a little outside the host's exclusion radius so the mounts sit clear of the hull.

None

Returns:

Type Description

list[int]: The ids created (may be shorter than count if any failed).

mount_set_offset(host, mount, offset)

Move a mount to a new body-frame offset.

The engine's connection carries its offset point from creation, so this deletes and re-adds it - cheap, and the only way to change where a mount rides.

mount_spawn(host, ship_key, offset=None, name='', side=None, behave_id='behav_station', delete_with_host=True)

Spawn a new object already welded to the host.

Spawns at the host's position and lets the weld pull it into place, so the caller never has to compute a world position - that is the engine's job now.

behav_station is the default because a mount must not steer: a behav_npcship would fight the tractor with its own helm.

Returns:

Type Description

int | None: The new mount's id, or None.