Skip to content

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.