Grav-tether
A tractor beam: lock / tow / reel a load, or swing a fighter around an anchor.
Thin wrappers over the engine-native tractor (sim.AddTractorConnection) plus the
mission-facing behavior the raw API doesn't provide — mode presets, a reel ramp, the
canonical impulse-only enforcement (cap / snap), and a moving circle-point swing.
Key facts (engine-confirmed): a static tether reels the target fully in, so holding a
load at a distance uses a per-tick rope-toggle; a player hull can be tractor-pulled.
Pair with closest_in_front for nose-aim acquisition (the engine has
no raycast).
Ask grav_tether_has, not grav_tether_get, for is this tethered?
grav_tether_get returns the live engine connection, and a Tow is a rope-toggle:
it deletes that connection whenever the load is inside the rope length and re-adds it
when the load drifts out. So get reads None for most of a perfectly good tow. A
menu gated on it offers Tow to something already under tow and never offers
Release — which is exactly what the shipped Weapons hold-click did. Use get only
when you want the connection object itself (to read .offset).
The offset point is not world-fixed - this module just never passes one
Older notes here said the tractor's offset point is world-fixed. It is measurably
source-relative: AddTractorConnection(host, target, vec3(0,0,200), 0) held a
target at exactly 200u and exactly 0 deg off the host's nose while the host's heading
swung 51 deg. A load tethered here always reels to the source's own position because
this module passes no offset - which is what a tow wants. To bolt something ON to
a hull instead of dragging it behind one, use mount.
Reach is opt-in, and so is the snap
grav_tether_set_range_limit(distance) sets how far a beam can reach to open; without
one there is no limit, which is what the library shipped with and what a mission that
says nothing still gets. An attach out of range is refused and emits
grav_tether_out_of_reach.
A live tether also snaps past SNAP_RANGE_FACTOR (1.5) times its hold distance,
emitting grav_tether_snapped. The hold distance is the longer of the beam's rope and
the engage range - not the rope alone, which is the tempting reading and is wrong:
a rope-toggle tow is supposed to sit beyond its rope, since that is the state in
which the pull engages, so a 500u tow reeling a load in from 2000 would snap itself on
the first tick.
A rigid lock across a gap is a teleport
Rigid means stiffness 0, and stiffness 0 has no rate limit - the engine puts
the load on the source point the same tick the connection is made. Close up, which is
the case grav_tether_lock was written for (a hangar recovery), that is exactly
right. At range it is a teleport, and once grav_tether_set_range_limit made a lock
from thousands of units away legal it became reachable from the shipped Weapons
hold-click.
It reads worst in the case the mass rule flips. Grav-lock a starbase and the station is the puller, so the end snapped across the gap is the player - reported from a bridge as "grav-lock a station from 7000u and you are instantly beside it". Measured: 7000u closed to 120u in a single 0.1s tick.
So a lock opened beyond LOCK_GRAB_DISTANCE (100u) engages at LOCK_WINCH_STIFFNESS
instead - a lagged, rate-limited pull - and hardens to rigid only once the load is
genuinely in reach, emitting grav_tether_locked. Same end state, arrived at
rather than jumped to. grav_tether_set_lock_grab_distance(d) retunes it.
The rule reads the live separation, not a ramped rope length: pull_distance is
not honored as a rest length, so a countdown would be a timer pretending to be a
measurement.
You can drag a starbase. It has to cost you.
A Lock and a Reel mass-reverse; a Tow deliberately does not. Grabbing a station rigidly means going where the station goes; hauling one means straining against it. Two different verbs, and a crew that picked Tow asked to be the one pulling - so a tow never flips, however outmatched, and pays for it instead.
It used to pay in one place only. _enforce_drag and _spend_tow_energy billed the
tug, while _tick_rope set a flat con.offset, so a 200-mass starbase reeled to
the rope at exactly the rate of a 1-mass fighter. The load was free; only the ship
holding it felt anything. Three changes, each on an axis a crew can act on:
The beam pulls slower under weight. _tow_lag divides the offset dial by
grav_tether_load_ratio(source, target) ** TOW_LAG_CURVE - a square root - with a
floor at TOW_LAG_MIN_SCALE (an eighth).
.offset is a SPEED, and it was documented backwards
Engine-measured on a controlled sweep (LM_TestRange map
test_tractor_calibrate: one source, identical targets at 30000u, raw
AddTractorConnection, offsets 1-80 read at 10s and 20s): the target closes at
offset x 30.2 units per second, linear in offset and linear in time. 20s
closes exactly twice what 10s closes at every value, so it is constant velocity -
not the first-order lag both this page and the mock assumed. 30.2 against a
30-tick second means the engine is almost certainly moving the target offset
units per tick.
So higher offset pulls FASTER. The engine API calls the field "stiffness" and the notes here called higher values "looser and laggier"; that was never measured and is the reverse of the truth. The first version of this feature therefore multiplied the dial for heavy loads and reeled a starbase in four times faster than a fighter - the exact opposite of its stated intent. Measured before and after on a real bridge, a light cruiser hauling a science station: 240 u/s before, 26 u/s after.
It was invisible because the mock modelled the pull as tau = offset x 1.2,
fitted qualitatively to a single point. A kinder-than-the-engine mock does not
merely miss bugs; here it confidently reported the inverse of the truth and every
test agreed with it.
Pull speeds at the base stiffness of 5: 151 u/s evenly matched, ~75 u/s on a freighter, ~26 u/s for a lone cruiser on a science station - which four cruisers lift back to ~52.
A square root because linear would put a 66:1 starbase grab at a fifteenth of base speed, slow enough that the power bill cuts the beam before the load has gone anywhere, so "you can drag a starbase" stops being true. The floor has to be strictly above zero: offset 0 is the rigid case, which puts the load on the source point in a single tick, so a mass table holding 100000 for a planet would otherwise divide the gentlest possible tow into a teleport.
A ratio of 1 or less returns the nominal offset untouched, and the whole thing
short-circuits when no mass provider is installed, so every existing tow - and every
mission with no mass table - is unchanged. It applies to MODE_TOW only: a swing's
anchor is a rock, and a lock on something heavy is reversed.
A second hull is worth bringing. grav_tether_pull_mass(target) is the combined
mass of everyone hauling it - grav_tether_pullers_of excludes a swing's anchor
and a reversed tether's registered source, neither of which is pulling anything, and
counting either would make the haul look lighter than it is.
grav_tether_load_ratio(source, target) is what the load is measured against. Pull
speed and drag both read it, and the energy bill is shared - each puller pays in
proportion to its own mass. Billing every ship the full amount, which
is what it did, means four hulls each drain at the solo rate and all cut out at the
same moment: four times the fleet's power for not one extra second of haul.
And it says so. grav_tether_strain(target) returns none / light / heavy /
overloaded, and crossing a band emits grav_tether_strain
(SOURCE_ID, TARGET_ID, STRAIN, RATIO, PULLERS). Edge-triggered - the tether tick
runs several times a sim-second and a signal at that rate is a flood, not feedback.
grav_tether_status carries strain and pullers for a readout. The band, not the
ratio, is the published number precisely because a console keyed on a per-tick value
repaints itself to pieces.
The bands sit where the mechanics change, not on round numbers: light ends where
drag stops growing (past there extra mass costs no extra drive - only lag and power),
and overloaded is where the beam's sluggishness rather than the drive penalty is
what is beating the crew.
A ship can be better at hauling than its hull says.
grav_tether_set_pull_bonus_fn(fn) installs fn(id) -> multiplier on what a ship
counts for when pulling. Deliberately not folded into the mass provider even
though the arithmetic is identical: mass also decides whether a Grav Lock reverses onto
you, what you cost somebody else to tow, and - for a mission pricing salvage by mass -
what your own wreck pays. Better towing gear that quietly raised the price of your hulk
would be a bug nobody would trace back to the rig.
LM ships two tiers of it. The Heavy Tug Rig is 4x, permanent, and bought at a station; the Tug Rig Mk I is 2.5x for ten minutes and is found in the world. They stack, and that is a fix rather than generosity: an item is decremented before its effect runs, so under a best-one-wins rule a crew that already owns the permanent rig would destroy any Mk I they activated for no benefit at all, with nothing able to refuse the press in time.
Two numbers worth the explanation. 4x means a rigged hull reads the same as a
four-ship team on every figure the beam computes - though not on endurance, since
the power bill is split between the ships actually on the beam and one rigged hull pays
all of it. And the Mk I is 2.5x rather than the obvious 2x because tow drag saturates
at a ratio of DRAG_FLOOR / DRAG_AT_EQUAL_MASS = 2.14: on the standard haul, a mass-3
cruiser dragging a mass-16 liner, a 2x rig only moves the ratio from 5.33 to 2.67 and
the drag penalty does not shift at all. 2.5x lands at 2.13, just under the floor, so
the penalty eases and the strain word drops a band.
Tow drag is what hauling costs - a reversed tether pays none of it
_enforce_drag drops the puller's impulse_upgrade_coeff and turn_upgrade_coeff by
the mass ratio. On a mass-reversed tether the caller is not hauling anything: they
are the load, and the engine is already moving their hull. Charging them anyway
stacked this module's two heaviest penalties on the one ship that had earned neither -
capped to impulse by _enforce_impulse and cut to the DRAG_FLOOR (a starbase is
20-60x a cruiser, which pins the amount at its 0.75 ceiling). That is what "the
engines stopped working while tethered" was. A swing is exempt for the mirror reason.
Warp is still refused, and always intentionally: impulse-only is canonical, so
_enforce_impulse clamps playerThrottle back to 1.0 every tick while a tether is
live. grav_tether_set_overspeed_default("snap") breaks the beam instead;
("off") drops the rule.
An anchor is never a load
ANCHOR_ROLES (black_hole,planet,nebula by default) names the bodies that may only
ever be the source end of a tether. Attaching one as the target is refused and
emits grav_tether_immovable.
This is not tidiness. A rigid grav_tether_lock on a black hole makes the hole the
load: the beam reels it onto the hull, _enforce_impulse caps the puller to impulse
so it cannot warp away, and a mission's lethal-proximity watch then explodes anything
within the kill radius - after which the hole stays parked wherever it was dropped.
One hold-click took whole games down that way.
A swing is unaffected and is the point of the distinction: there the anchor is the
source and the ship is what moves, so a black-hole slingshot still works. A mission
that wants the old behavior calls grav_tether_set_anchor_roles(""); one that has
other immovable bodies adds them.
For a readout, ask grav_tether_status
A display needs three facts at once: what is on the other end, what the beam is
doing, and which end this ship is on. grav_tether_status(obj) answers all
three (partner / mode / role), or None when the ship is free;
grav_tether_partner(obj) is the id alone, for a route that wants something to hand
straight to to_object.
role is the part that is easy to skip and wrong to skip: a fighter on a swing
is the tethered target, not the puller, and so is any ship caught in a hostile
beam - or one that grabbed a load heavy enough for the mass rule to reverse the
connection. A panel that assumes "I am the puller" is confidently backwards exactly
when being tethered matters most. LM's Weapons readout
(consoles/tether_indicator.py) is the worked example.
API
grav_tether — attach a beam between two space objects to lock / tow / reel a load.
Thin wrappers over the ENGINE-native tractor system (sim.AddTractorConnection /
DeleteTractorConnection / GetTractorConnection) plus the mission-facing behavior
the raw API doesn't provide: the mode presets (lock / tow / reel), a reel ramp, and the
canonical impulse-only enforcement.
Confirmed in-engine (Phase 0 spike, GRAV_TETHER_PLAN.md):
* AddTractorConnection(src, tgt, offset_point, pull_distance) pulls tgt toward
src + offset_point; pull_distance is a rope rest-length (the target
settles at that distance).
* the connection's .offset float is a pull SPEED dial, engine-measured at
offset x 30.2 u/s, linear in offset and linear in time: 0 = rigid lock (the
target is placed on the point in one tick), ~5 = a good taut tow at ~151 u/s, and
higher pulls FASTER. Earlier notes here called it stiffness and said higher was
"looser/laggier" - never measured, and backwards. See LM_TestRange map
test_tractor_calibrate.
* a tether can only hold at impulse — warp (playerThrottle > 1) outruns the
rate-limited pull. Canonical (old-game Arena a28 precedent). Enforced here per
tether: cap (default, governs the source back to impulse) or snap (breaks
the tether and drops the load).
NOTE: the cosmos_dev mock STORES connections but does not simulate the pull, so the physics is engine-verified; the registry / enforcer / reel logic below is Python and IS unit-tested against the mock.
THE OFFSET POINT IS SOURCE-RELATIVE - it rotates with the hull. Engine-measured (LM_TestRange/maps/test_tractor_mount.mast): AddTractorConnection with an offset held a target at exactly 200u and exactly 0 deg off the source's nose through a 51 deg turn. Older notes here and in GRAV_TETHER_PLAN.md called it "world-fixed"; that was only ever true of the case this module uses, because a tow passes NO offset and the load therefore reels to the source's own position. The wrong wording cost a real design decision once. To bolt something ONTO a hull rather than drag it behind one, use sbs_utils.procedural.mount - which shares the engine call but deliberately not this registry, since _enforce_impulse would cap the carrying ship to impulse.
grav_tether_attach(source, target, offset=None, stiffness=0.0, pull_distance=0.0, overspeed=None)
Open (or replace) a tether so source pulls target.
offset - point (relative to source) the target is pulled toward. stiffness - the connection's .offset dial: 0 = rigid lock, ~5 = taut tow. pull_distance - rope rest-length; the target settles at this distance. overspeed - per-tether enforcement mode; None uses the module default. Returns the tractor_connection, or None if either object is missing.
grav_tether_between(a, b)
Whether these two are tethered to each other, whichever way round.
grav_tether_has is directional, and a SWING is registered the other way up (the
anchor is the source and the ship is the load). So a menu that asks has(me, that)
is blind to a swing it opened itself: it goes on offering the grab and never offers
Release, and the crew cannot let go. Ask this instead whenever the question is "is
there a beam between these two", not "am I the puller".
grav_tether_clear_all()
Drop all tethers (fresh mission / test reset).
Drops OUR tethers one by one rather than calling ClearTractorConnections(), which is
global: the engine has a single tractor pool, and other systems build connections in
it that are not tethers. procedural.mount welds a turret to a hull with one, and a
global clear silently unwelded every mount while mount's own bookkeeping went on
insisting they were attached. Deleting only what this module registered keeps the two
uses independent.
Tolerates having no sim: this runs from reset_mission_state(), which can fire with no frame context at all, and dropping our own state must never depend on the engine being there. The engine-side connections die with the old sim regardless.
grav_tether_get(source, target)
Return the live tractor_connection for the pair, or None.
grav_tether_has(source, target)
True if this exact PAIR is tethered — ask this, not :func:grav_tether_get.
grav_tether_get returns the live ENGINE connection, and a Tow is a rope-TOGGLE: it
deletes the connection whenever the load is inside the rope length and re-adds it when
the load drifts out. So get reads None for most of a perfectly good tow, and a UI
gated on it offers "Tow" to something already under tow and never offers "Release".
Use get only when you want the engine object itself (to read .offset).
grav_tether_involves(obj)
True if obj is either end (source or target) of any live tether — for a one-button toggle where the ship may be the puller (tow/reel) or the pulled (swing).
Registry-based, so it is honest during a rope-toggle tow (see :func:grav_tether_has).
grav_tether_is_anchor(obj)
Whether this object can only ever be the anchor end of a tether.
grav_tether_load_ratio(source, target)
How outmatched the ships on target are, all of them together.
The team-aware sibling of :func:grav_tether_mass_ratio, which stays a ONE-ship
question on purpose: who gets reversed is about the ship that grabbed, while how hard
the haul is is about every beam on the load. Every cost a tow pays comes from this,
so a tug joining lightens the haul for everyone already on it.
source is only the fallback: when nothing is registered as hauling the target -
a caller asking before the tether exists, or from the reversed end - it stands in for
the crew, so the answer is the plain one-ship ratio rather than a wrong team one.
grav_tether_lock(source, target, offset=None, overspeed=None)
Rigid grab: target locked onto the source's offset point (cargo, hangar recovery).
A lock opened across a GAP winches in first. Rigid means stiffness 0, and stiffness 0
has no rate limit anywhere - engine or mock - so the connection puts the load on the
source point the same tick it is made. Close up (a hangar recovery, the case this mode
was written for) that is exactly right and nothing changes. At range it is a teleport,
and once :func:grav_tether_set_range_limit let a lock open from thousands of units
away it became reachable from the shipped Weapons hold-click. Worse in the one case
that reads as broken rather than cheap: the mass rule flips a grab on a starbase, so
the STATION is the puller and the PLAYER is what gets snapped across the gap.
So beyond :data:LOCK_GRAB_DISTANCE the beam engages at
:data:LOCK_WINCH_STIFFNESS - a lagged, rate-limited pull - and _tick_lock
hardens it to rigid once the load is actually in reach, emitting
grav_tether_locked. Same end state, arrived at rather than jumped to.
grav_tether_lock_grab_distance()
The rigid-grab distance in force.
grav_tether_mass(obj)
What this object weighs, via the installed provider. Never returns 0.
grav_tether_mass_ratio(source, target)
target mass / source mass. >1 means the LOAD is the heavier end.
The one number the constraints layer turns on: who drags whom, and how much it costs the puller.
grav_tether_mode(source, target)
Which preset opened this tether - "lock", "tow", "swing", "reel"
- or None when the pair is not tethered.
grav_tether_out_of_reach(source, target)
Whether these two are too far apart to open a tether.
grav_tether_partner(obj)
The id on the other end of obj's tether, or 0 when it is free.
The MAST-friendly half of :func:grav_tether_status: an id is something a route can
hand straight to to_object / has_any_role without unpacking a dict.
grav_tether_pull_bonus(source)
This ship's hauling multiplier. Never returns 0 and never raises.
grav_tether_pull_mass(target)
Combined mass of every ship hauling target, tug rigs included.
A load does not know how many ropes are on it - it knows how hard it is being pulled. So the number that matters to a haul is the total on the beam, not any one tug's share, and this is what makes a second hull worth bringing.
Falls back to :data:DEFAULT_MASS when nothing is hauling it, so a caller asking
about a free object gets the same neutral answer :func:grav_tether_mass gives.
grav_tether_pullers_of(target)
The ships actually HAULING target - what a readout counts.
:func:grav_tether_sources_of minus two kinds of beam that are registered as sources
and are pulling nothing. A SWING's source is the anchor, and a rock does not haul. A
MASS-REVERSED tether's registered source is the LOAD - the engine is moving them.
Counting either inflates the crew and makes the haul look lighter than it is.
grav_tether_range_limit()
The engage range in force, or None.
grav_tether_reel(source, target, rate=DEFAULT_REEL_RATE, stiffness=DEFAULT_TOW_STIFFNESS, offset=None, overspeed=None)
Reel the load in: start the rope at the current separation and ramp it to 0,
then emit grav_tether_reeled for the caller to hand off (collect / dock).
grav_tether_release(source, target)
Break a single tether (source no longer pulls target). Safe if none exists.
grav_tether_release_all(source)
Break every tether where source is the puller.
grav_tether_release_any(obj)
Release every tether obj is part of, at either end.
grav_tether_release_between(a, b)
Break the tether between these two, whichever end opened it. Safe if none exists.
grav_tether_rope(source, target, rope_len, stiffness=DEFAULT_TOW_STIFFNESS, overspeed=None)
Hold the target at ~rope_len from the source via a per-tick ROPE-TOGGLE:
beyond rope_len a stiff pull snaps it back to the circle; inside, the tether is
released so it moves free. Engine-confirmed (data harness): a STATIC tether reels
the target fully in regardless of pull_distance (1500 -> ~165), so holding a load
at a distance REQUIRES this toggle (which held 798/801/801 at rope_len=800). Both
Tow (source drags a trailing load) and Swing (anchor holds the ship) are this same
rope-hold — only the source/target roles differ.
grav_tether_set_anchor_roles(roles)
Set the roles that may never be PULLED (comma-separated, or "" to allow all).
A mission that wants the library default back passes :data:ANCHOR_ROLES.
grav_tether_set_attach_policy(fn)
Install (or clear with None) the attach veto callback. An attach whose fn(source_id, target_id) returns False is refused (attach returns None).
grav_tether_set_grab_speed_limit(limit)
Refuse a grab on anything moving faster than limit throttle. None = no rule.
grav_tether_set_lock_grab_distance(distance)
How close a Grav Lock may go rigid from. Beyond it, a lock winches in first.
grav_tether_set_mass_fn(fn)
Install (or clear with None) the mass provider: fn(id) -> float.
Without one every object weighs :data:DEFAULT_MASS, so the mass rules below all
reduce to "evenly matched" - no gating, no drag. That is deliberate: a library that
guessed at mass would be confidently wrong, and a mission that has not said what
things weigh should get the un-gated behavior it had before.
grav_tether_set_overspeed_default(mode)
Set the module default overspeed mode (cap / snap / off) for new tethers.
grav_tether_set_pull_bonus_fn(fn)
Install (or clear with None) the hauling-bonus provider: fn(id) -> multiplier.
1.0 is an ordinary hull. This is how a mission gives a ship a heavy-tug rig without lying about what it weighs.
DELIBERATELY NOT FOLDED INTO THE MASS PROVIDER, even though the arithmetic would be
identical, because mass answers three other questions and a tug rig should change
none of them: whether a Grav Lock reverses onto you (:data:MASS_REVERSE_RATIO),
what you cost somebody ELSE to tow, and - for a mission that prices salvage by mass -
what your own wreck is worth. Better towing gear that quietly made your hulk more
valuable would be a bug nobody would ever trace back to the rig.
grav_tether_set_range_limit(distance, snap_factor=SNAP_RANGE_FACTOR)
Set how far a tether can reach to open, and how far it stretches before it snaps.
None clears the rule and restores the library's original unlimited reach.
grav_tether_set_tow_energy_cost(per_mass_per_tick)
Energy the puller spends per tick, per unit of towed mass. 0 = free.
grav_tether_sources_of(target)
List the source ids currently tethering target (a tow/lock source, or a swing
anchor). Lets a mission see who is working a shared quest target (claim-on-tether).
grav_tether_status(obj)
What obj is tethered to and how, or None when it is free.
One call rather than three, because every readout wants the same three facts at once: what is on the other end, what the beam is doing, and which end WE are on. A ship can be the puller (tow/reel/lock) or the pulled (swing, or a grab that mass reversed), and a display that assumes the first is wrong exactly when being tethered matters most.
Returns a dict {"partner", "mode", "role", "source", "target", "strain",
"pullers"} - role is "source" when obj is the puller, "target" when it
is the load. The FIRST tether found; a ship holding several is unusual and a
one-square readout has room for one anyway.
strain and pullers are here so a console can say what a haul is costing
without reaching into this module's internals - the whole reason a tow felt broken
was that nothing surfaced them. strain is deliberately the BAND, not the ratio:
a readout keyed on a per-tick number repaints itself to pieces.
grav_tether_strain(source, target)
How hard the ships on target are working, as a word a readout can print.
none / light / heavy / overloaded. The boundaries are the points where
the mechanics actually change, not round numbers: light ends where drag stops
growing (past there extra mass no longer costs extra drive - only lag and power), and
overloaded is where the beam's own sluggishness, not the drive penalty, is what is
beating the crew.
A BAND rather than the raw ratio on purpose. The ratio moves whenever anything joins or leaves; a band moves about once a haul, which is what lets it drive both an edge-triggered signal and a console's repaint key without tearing the panel down.
grav_tether_swing(anchor, ship, rope_len, stiffness=1.0, overspeed=None)
Fighter swing (SECONDARY): hold the ship on a CIRCLE of radius rope_len around the anchor so it orbits on its own throttle. A plain rope-toggle pulls toward the anchor center, which has no centrifugal balance and spirals the ship in (measured 758→663). Instead each tick we aim the pull at the point on the circle at the ship's CURRENT bearing — a purely radial correction that holds the radius without killing tangential motion. Engine-confirmed a player hull can be tractor-pulled; final feel is a fly-it.
grav_tether_target_too_fast(target)
Whether this target is moving too fast to get hold of.
grav_tether_targets_of(source)
List the target ids source is currently pulling. Mirror of
:func:grav_tether_sources_of.
grav_tether_tick(t=None)
Runs on the TickDispatcher (~7.5/sim-sec in-engine, 6 in the mock - see DEFAULT_REEL_RATE) while any tether is live; also directly callable (tests). Enforces impulse and advances reels; self-heals dead objects.
grav_tether_tow(source, target, distance, stiffness=DEFAULT_TOW_STIFFNESS, overspeed=None)
Trailing tow: hold the load at ~distance from the source via the rope-toggle
(a static tether would reel it fully in). As the source moves, the load trails behind
at that distance - no offset needed here; the drag makes it trail for free.
NOTE the offset point is only "world-fixed" in the sense that THIS module never
passes one. Engine-measured (LM_TestRange/maps/test_tractor_mount.mast):
AddTractorConnection(host, target, vec3(0,0,200), 0) holds the target in the
SOURCE'S BODY FRAME - exactly 200u at exactly 0 deg off the nose while the host's
heading swung 51 deg. Passing no offset is what makes a load reel to the source's own
position, which is what a tow wants and what the "reels fully in regardless of
pull_distance" measurement was really showing. To bolt something ON to a hull rather
than drag it behind one, use :mod:sbs_utils.procedural.mount.