Documentation
¶
Overview ¶
Package sim — v0.11.0+ launch-trail breadcrumb buffer.
LaunchTrail is the body-fixed (lat, lon, alt, sampledAt) FIFO that the chase-cam scene re-projects each render so the trace visibly rotates with the body — the geographic launch site stays geographic. Stored on World; cleared at session open / switch-end release / hand-off; survives a manual `v` cycle out + back.
Plan reference: designdocs/terminal-space-program/v0.11-plan.md → Slice v0.11.0 (Trail is body-fixed locked decision).
Package sim — v0.11.0+ ViewLaunch state machine.
ViewLaunch is the chase-cam launch scene routed into automatically when an active vessel transitions Landed false→true (Launchpad spawn today; future Touchdown semantics will extend the trigger). Leaving the chase cam is a manual `v` cycle — ADR 0021 D retired the apoapsis-floor auto-release, the one camera change driven by ambient sim state. (The ORBIT READY callout and the ViewTilted LaunchAnchor keep their LaunchMissionFloorM gate; only the view restore went.) A session still ends without a `v` press when the player switches active onto a flying vessel — that restore answers the player's switch, not ambient sim state.
This file owns the per-tick handler called from World.Tick and the session-open/close helpers. Render-side (the chase-cam scene itself) lives in internal/tui/screens/launch.go.
Plan reference: designdocs/terminal-space-program/v0.11-plan.md → Slice v0.11.0. Architectural rationale: designdocs/terminal-space-program/adr/0002-launch-view-as-distinct-viewmode.md (auto-release clause superseded by adr/0021-player-owned-camera-local-to-body-arcs.md, decision D).
Index ¶
- Constants
- Variables
- func CompositeEngineSummary(c *spacecraft.Spacecraft) (totalThrust, weightedIsp float64)
- func LaunchAnchorPhi(c *spacecraft.Spacecraft, el orbital.Elements, ok bool) (phi float64, active bool)
- func LoadGroundStations() ([]GroundStationPreset, []GroundStationWarning)
- func LoadGroundStationsWithWarnings() ([]GroundStationPreset, []GroundStationWarning)
- func PlaceAcrossSubspaceGap(c *spacecraft.Spacecraft, dtSeconds float64) bool
- func RetagStackForTransfer(c *spacecraft.Spacecraft, fromOwner, toOwner string)
- func SafeHandback(c *spacecraft.Spacecraft)
- func SeparationPush(c *spacecraft.Spacecraft)
- func StackGuestOwner(c *spacecraft.Spacecraft) (string, bool)
- func StackHasGuest(c *spacecraft.Spacecraft) bool
- func StackMidBurn(c *spacecraft.Spacecraft) bool
- func SystemNames(systems []bodies.System) []string
- func TickCmd(d time.Duration) tea.Cmd
- func TransposeReady(c *spacecraft.Spacecraft) bool
- type ActiveBurn
- type AttitudeIntent
- type AutoWarpTarget
- type CapturePreview
- type ChatLine
- type CircularizePlan
- type Clock
- type CoWarpCraft
- type CoWarpPeer
- type CoWarpResult
- type CoWarpState
- type CommDisconnectReason
- type CommGraph
- type DockEvent
- type DockGuestLink
- type Focus
- type FocusKind
- type FrameTransition
- type Ghost
- type GroundStationPreset
- type GroundStationWarning
- type HohmannPreview
- type LaunchReleaseEvent
- type LaunchSitePreset
- type ManeuverNode
- type ManualBurn
- type NavMode
- type NavballBasis
- type PredictedLeg
- type RCSPuffSample
- type RendezvousArm
- type RendezvousArrival
- type RendezvousInvite
- type RendezvousRateHolder
- type RendezvousRateState
- type RendezvousSeat
- type RendezvousWait
- type RendezvousWaitReason
- type RendezvousWaypoint
- type SOIPass
- type SOISegment
- type SessionEvent
- type SessionEventKind
- type SessionInfo
- type SessionInvite
- type SessionPlayer
- type SpawnSpec
- type StartScenario
- type SyncArrival
- type Target
- type TargetApproach
- type TargetKind
- type TickMsg
- type TrailPoint
- type TransferComparison
- type TransferOptions
- type TriggerEvent
- type ViewMode
- type ViewTilt
- type World
- func (w *World) ActiveCommPath() (points []orbital.Vec3, hops int, connected bool)
- func (w *World) ActiveCraft() *spacecraft.Spacecraft
- func (w *World) ActiveMission() *missions.Mission
- func (w *World) AdjustStartForLunarTransferWindow(lead time.Duration) bool
- func (w *World) AdjustThrottle(delta float64)
- func (w *World) AdoptCraft(c *spacecraft.Spacecraft, makeActive bool) int
- func (w *World) ApplyStartScenario(s StartScenario) error
- func (w *World) ArrivalCapturePreview() (CapturePreview, bool)
- func (w *World) AutoWarpEligible() bool
- func (w *World) AutoWarpEngaged() bool
- func (w *World) AutoWarpSecondsToTarget() (float64, bool)
- func (w *World) BodyPosition(b bodies.CelestialBody) orbital.Vec3
- func (w *World) BodyPositionAt(b bodies.CelestialBody, t time.Time) orbital.Vec3
- func (w *World) BodySOIRadius(b bodies.CelestialBody) float64
- func (w *World) CanCommandCraft(c *spacecraft.Spacecraft) bool
- func (w *World) ClearNodes()
- func (w *World) ClearTarget()
- func (w *World) CoWarpCoupled() bool
- func (w *World) CommBandCoverage(bodyID string, altM, antennaRangeM float64) (float64, bool)
- func (w *World) CommBlockedFlash() (string, bool)
- func (w *World) ComputeCoWarp(peers []CoWarpPeer, prev map[string]bool) CoWarpResult
- func (w *World) CounterfactualSOIPass() (SOIPass, bool)
- func (w *World) CraftByID(id uint64) (*spacecraft.Spacecraft, int, bool)
- func (w *World) CraftInertial() orbital.Vec3
- func (w *World) CraftInertialVelocity(c *spacecraft.Spacecraft) orbital.Vec3
- func (w *World) CraftTrail() []orbital.Vec3
- func (w *World) CraftVisibleHere() bool
- func (w *World) CycleActiveCraft(delta int)
- func (w *World) CycleEngineMode()
- func (w *World) CycleFocus(forward bool)
- func (w *World) CycleNavMode() NavMode
- func (w *World) CycleRCSPulseScale()
- func (w *World) CycleSystem()
- func (w *World) CycleTarget(forward bool)
- func (w *World) CycleViewMode()
- func (w *World) DeleteNode(idx int)
- func (w *World) DensifyForeignArcs(segs []SOISegment) []SOISegment
- func (w *World) Deploy(idx int) bool
- func (w *World) DisengageAutoWarp()
- func (w *World) DisengageRendezvousWarp()
- func (w *World) DockCrafts(idxA, idxB int)
- func (w *World) DockGuestCraft(dockerIdx int, guest *spacecraft.Spacecraft, guestOwner string) (*spacecraft.Spacecraft, int, bool)
- func (w *World) DockGuestStackGhost() (Ghost, *bodies.CelestialBody, bool)
- func (w *World) DockOwnerOnline() bool
- func (w *World) DriveRendezvousWarp(peers []CoWarpPeer)
- func (w *World) EffectiveWarp() float64
- func (w *World) EndFlightActive() bool
- func (w *World) EndRendezvousOnDock(partner string) bool
- func (w *World) EngageAutoWarp() bool
- func (w *World) EngageRendezvousWarp(partner, handle string, tau time.Time, committedCA float64) bool
- func (w *World) EngageRendezvousWarpAs(partner, handle string, tau time.Time, committedCA float64, initiator bool) bool
- func (w *World) EngageSyncWarp(target time.Time, owner, handle string) bool
- func (w *World) EnsureCraftIDs()
- func (w *World) EnsureNodeIDs()
- func (w *World) EntryPosition(p SOIPass) orbital.Vec3
- func (w *World) ExitPosition(p SOIPass) orbital.Vec3
- func (w *World) FireRCSPulse(mode spacecraft.BurnMode) bool
- func (w *World) FocusIsEncounterFramed() bool
- func (w *World) FocusName() string
- func (w *World) FocusPosition() orbital.Vec3
- func (w *World) FocusZoomRadius() float64
- func (w *World) FocusedBody() (bodies.CelestialBody, bool)
- func (w *World) FollowDockGuestStack()
- func (w *World) GuestReleaseRefusal(idx int) string
- func (w *World) HasRelativeTarget() bool
- func (w *World) HohmannDepartureWarning(targetIdx int) string
- func (w *World) HohmannPreviewFor(bodyIdx int) HohmannPreview
- func (w *World) IterateBurnDV(mode spacecraft.BurnMode, dvGuess float64) (float64, error)
- func (w *World) LiveSOIPass() (SOIPass, bool)
- func (w *World) MissionFailFlash() (string, bool)
- func (w *World) MissionProgramEnabled(program string) bool
- func (w *World) NavballBasis() (NavballBasis, bool)
- func (w *World) NavballMarkers() []render.NavballMarker
- func (w *World) NavballSubObserver() (latDeg, lonDeg float64, ok bool)
- func (w *World) NextFrameTransition() (FrameTransition, bool)
- func (w *World) NodeInertialPosition(n ManeuverNode) orbital.Vec3
- func (w *World) NudgeLaunchZoom(dir int, currentAutoScale float64)
- func (w *World) NudgeViewTiltPhi(deltaDeg float64) float64
- func (w *World) NudgeViewTiltTheta(deltaDeg float64) float64
- func (w *World) PeerRange(p CoWarpPeer) (float64, bool)
- func (w *World) PerilunePosition(p SOIPass) orbital.Vec3
- func (w *World) PlanCircularizeAtApoapsis() (*CircularizePlan, error)
- func (w *World) PlanInclinationChange(targetIncl float64) (*planner.InclinationPlan, error)
- func (w *World) PlanNode(n ManeuverNode)
- func (w *World) PlanPlaneMatch(targetIdx int) (*planner.InclinationPlan, error)
- func (w *World) PlanRendezvousNudge() (*planner.RendezvousAdvisory, error)
- func (w *World) PlanTransfer(targetIdx int) (*planner.TransferPlan, error)
- func (w *World) PlanTransferAt(targetIdx int, depDay, tofDay float64, opts TransferOptions) (*planner.TransferPlan, error)
- func (w *World) PlannedSOIPass() (SOIPass, bool)
- func (w *World) PorkchopGrid(targetIdx int, depDays, tofDays []float64, opts TransferOptions) ([][]float64, error)
- func (w *World) PostBurnState(n ManeuverNode) (physics.StateVector, string)
- func (w *World) PredictedFinalOrbit() (physics.StateVector, bodies.CelestialBody, bool)
- func (w *World) PredictedLegs() []PredictedLeg
- func (w *World) PredictedSegmentsFrom(post physics.StateVector, startPrimary bodies.CelestialBody, ...) []SOISegment
- func (w *World) PredictedTargetApproach() (TargetApproach, bool)
- func (w *World) PreviewBurnState(mode spacecraft.BurnMode, dv float64, duration time.Duration, ...) (physics.StateVector, bodies.CelestialBody, bool)
- func (w *World) RCSActive() bool
- func (w *World) RCSPuffs() []RCSPuffSample
- func (w *World) RecommendedRendezvousBurn() (planner.RendezvousAdvisory, bool)
- func (w *World) RecomputeCommGraph()
- func (w *World) RecordAction(a missions.Action)
- func (w *World) RefinePlan() (correctionDv, arrivalDv float64, err error)
- func (w *World) RemoveCraftByID(id uint64) (*spacecraft.Spacecraft, bool)
- func (w *World) RendezvousApproachPhase() bool
- func (w *World) RendezvousCommit() (tau time.Time, ca float64, ok bool)
- func (w *World) RendezvousInitiatorSeat() bool
- func (w *World) RendezvousNeedsBurnToClose(ca float64) bool
- func (w *World) RendezvousRateHold() RendezvousRateHolder
- func (w *World) RendezvousSeatBurning() bool
- func (w *World) RendezvousSeatRate() float64
- func (w *World) RendezvousWarpEngaged() bool
- func (w *World) ResetFocus()
- func (w *World) ResolveAttitudeIntent(intent AttitudeIntent) spacecraft.BurnMode
- func (w *World) ResolveTargetCraft() (*spacecraft.Spacecraft, int, bool)
- func (w *World) ResolveTargetGhost() (Ghost, bodies.CelestialBody, bool)
- func (w *World) SegmentDrawPoints(seg SOISegment, homeID string) []orbital.Vec3
- func (w *World) SetActiveCraftIdx(idx int)
- func (w *World) SetAttitudeMode(mode spacecraft.BurnMode)
- func (w *World) SetEnabledMissionPrograms(programs map[string]bool)
- func (w *World) SetTargetBody(idx int)
- func (w *World) SetTargetCraft(idx int)
- func (w *World) SetTargetGhost(owner string, craftID uint64)
- func (w *World) SetThrottle(t float64)
- func (w *World) SetViewModeLaunch()
- func (w *World) SpawnCraft(spec SpawnSpec) (*spacecraft.Spacecraft, error)
- func (w *World) SpawnSisterCraft() (*spacecraft.Spacecraft, error)
- func (w *World) SpectateGhost(owner string, craftID uint64)
- func (w *World) StageActive(craftIdx int) (newActiveIdx, jettisonedIdx int, err error)
- func (w *World) StartManualBurn()
- func (w *World) StepRendezvousBrake(up bool) (float64, bool)
- func (w *World) StopManualBurn()
- func (w *World) SwitchToCraftIdx(idx int) bool
- func (w *World) System() bodies.System
- func (w *World) TargetName() string
- func (w *World) TargetState() (orbital.Vec3State, bool)
- func (w *World) TargetStateRelativeToActivePrimary() (rT, vT orbital.Vec3, ok bool)
- func (w *World) Tick()
- func (w *World) ToggleAutoWarp() bool
- func (w *World) ToggleInstantSAS()
- func (w *World) ToggleManualBurn()
- func (w *World) Transpose(craftIdx int) error
- func (w *World) Undock(idx int) bool
- func (w *World) UndockGuest(compositeIdx int, guestOwner string, guestCraftID uint64) (*spacecraft.Spacecraft, bool)
- func (w *World) UndockRefusal(idx int) string
Constants ¶
const ( CoWarpCoupleRangeM = coWarpCoupleRangeM CoWarpCoupleSpeedMs = coWarpCoupleSpeedMs )
CoWarpCoupleRangeM / CoWarpCoupleSpeedMs are the exported form of the couple gate (ADR 0037 §5). The neighbourhood rule was documented and nowhere else: nothing in the game named the range or the closing speed at which two players' warps lock together, so a pilot on approach could only discover it by crossing it. The Session roster and the F1 help overlay state the rule in these numbers, so the display can never drift from what the sim gates on.
const ( DockingDistM = 50.0 // metres — KSP-ish "soft capture" distance. DockingVMS = 0.1 // m/s — typical proximity-ops null-residual. )
Docking proximity gates. Craft within DockingDistM and below |v_rel| = DockingVMS while sharing the same primary frame fuse into a single composite at the next tick. v0.8.3+.
const ( // CrashVCritMps is the impact-velocity ceiling for a soft // landing. Falcon 9 / Apollo LM real touchdowns settle around // 1–2 m/s; uncontrolled free-fall onto a body at low orbital // altitudes is hundreds of m/s. 10 m/s is the starting value // (retunable from playtest). CrashVCritMps = 10.0 // CrashNoseTol is the minimum nose-alignment dot product // (CurrentAttitudeDir · localUp) for a soft landing. 0.7 ≈ 45° // off local vertical — a Falcon 9 with the nose past 45° from // up isn't going to land softly; flag as crash. CrashNoseTol = 0.7 // ChuteDeployQMin is the dynamic-pressure floor (Pa) at which an // ARMED parachute auto-deploys (v0.12 Slice 3, ADR 0008). The deploy // gate is expressed in q = 0.5·ρ·|v_rel|², which is body-agnostic — // no body-specific deploy-altitude constant. 1000 Pa is the starting // value: low enough that a capsule re-entering through thickening air // reaches it well before the surface (giving the canopy time to bleed // off velocity), high enough that a chute armed in near-vacuum at the // atmosphere's edge doesn't pop the instant q lifts off zero. // Retunable from playtest like V_CRIT / NOSE_TOL. ChuteDeployQMin = 1000.0 )
const ( TriggerAbsolute = spacecraft.TriggerAbsolute TriggerNextPeri = spacecraft.TriggerNextPeri TriggerNextApo = spacecraft.TriggerNextApo TriggerNextAN = spacecraft.TriggerNextAN TriggerNextDN = spacecraft.TriggerNextDN TriggerNextClosestApproach = spacecraft.TriggerNextClosestApproach )
const ( )
Navball glyphs. Mirroring KSP's symbol vocabulary so muscle memory transfers. Single-cell unicode chars from the Geometric Shapes block, picked to read distinctly at small disk sizes.
const ( AdvisoryKeyRendezvousNudge = "rendezvous-nudge" AdvisoryKeyCircularize = "circularize" )
Advisory key tags (#293). PlanRendezvousNudge (K) and PlanCircularizeAtApoapsis (C) stamp their planted node's spacecraft.ManeuverNode.AdvisoryKey with one of these so a repeat press replaces its own previous unfired node instead of stacking a stale duplicate behind it — see replaceAdvisoryNode.
const ( TargetNone = spacecraft.TargetNone TargetBody = spacecraft.TargetBody TargetCraft = spacecraft.TargetCraft TargetSite = spacecraft.TargetSite TargetGhost = spacecraft.TargetGhost )
Re-exported constants — preserve the `sim.TargetNone` etc. identifiers that 75+ readers depend on.
const ( ViewTiltThetaMinDeg = 0.0 ViewTiltThetaMaxDeg = 60.0 ViewTiltThetaStep = 5.0 )
ViewTiltThetaMinDeg / ViewTiltThetaMaxDeg clamp the player's shift+up / shift+down nudges. 0° collapses the tilt back to the world-axis ViewTop projection (identity rotation); 60° pushes foreshortening to where the orbit ellipse starts to read as a horizontal cigar. Step is 5° per press.
const CoWarpSubspaceTolerance = time.Duration(coWarpSubspaceToleranceSec) * time.Second
CoWarpSubspaceTolerance is the exported form of the same-subspace gate (v0.29 S2): the Session screen's Rendezvous Warp row action refuses a partner whose |Δt| exceeds it ("Sync first") so the arm can actually couple rather than sit dead across a subspace divergence.
const ( // CommBandDegradedThreshold: sampled coverage below this flags the // preset. Fractionally under 1.0 to absorb grid-edge noise, so the // blanket / geosync cases read clean. CommBandDegradedThreshold = 0.995 )
const DefaultGroundStationRangeM = 5.0e9
DefaultGroundStationRangeM is the rated range a station falls back to when its entry carries no positive range — e.g. a user overlay authored before the power_w→range_m rename (ADR 0027 §2 amendment), whose old key now unmarshals to 0. Keeps a mis-keyed / un-ranged station functional at the DSN tier instead of silently dead (commLinked rejects range<=0). The embedded ring always carries explicit ranges, so this only ever rescues overlays.
const DefaultLaunchpadLatitude = 28.6083
DefaultLaunchpadLatitude is the spawn latitude the form uses when the player opens it without changing the field. v0.9.2+: pinned to LC-39A (Kennedy Space Center, the historical Saturn V launch pad) at 28.6083°N. Applied at the form layer, not in SpawnCraft, so API callers can explicitly spawn at the equator with Latitude=0.
const DefaultLaunchpadLongitudeEast = -80.604
DefaultLaunchpadLongitudeEast is the spawn longitude offset (deg east of pseudo-Greenwich) for the form's default "Cape Canaveral" preset. -80.604° matches LC-39A's Earth-relative longitude. v0.9.2+.
const DefaultLunarTransferLead = 4 * time.Hour
DefaultLunarTransferLead is how far before the next ideal Moon-transfer departure a fresh game should start. Short enough to reach by warping a little, long enough to look at the system and set up the burn first.
const LaunchMissionFloorM = 200_000.0
LaunchMissionFloorM is the apoapsis altitude (m) at which the launch-anchor releases. Same value as the saturn-v-pad-to-leo mission floor and the ORBIT READY callout's gate — single source of truth lives here. Mirrors internal/missions/missions.json:40. orbit.go's LAUNCH HUD vanish-gate (`shouldShowLaunchHUD`) reads this same constant via a sim import.
const ReArmCeiling = 10 * time.Minute
ReArmCeiling bounds how long a re-arm latch may hold when the distance it waits on can never be read (#326). The latch clears on a POSITIVE range reading, and a partner who is landed, in another system, in another SOI, or simply not reporting yields no reading at all — so the ordinary reason to undock (one of you leaves) is exactly the case that holds the latch open forever, and a restart re-seeds it from disk with an empty report store.
Measured in SIM-time against the release stamp the record already carries, not wall time: the latch is a statement about the pair's flight ("back away first"), and sim-time is the clock that flight happens on — under warp a pair who have not separated in ten minutes of flying are not about to. It costs nothing durable, since the stamp is a field the record already persists. Far beyond ADR 0038 §5's "silent instant re-fuse" worry, so it cannot expire a latch that is still doing its job; short enough that a vessel is never un-dockable for a session.
const ReArmDistM = DockingDistM * 2 // 100 m
ReArmDistM is the re-arm-by-leaving distance (ADR 0038 §5): once a cross-player pair undocks, a fresh Claim() between the SAME pair of craft is refused until they separate past this distance — "back away first, then you can dock again," a rule with no invisible timer for a drift-back to silently outlast. Deliberately past DockingDistM's 50 m gate (the ADR's "~100 m, just outside the gate band") so re-arming isn't a coin-flip right at the boundary the 75 m SeparationPush lands just inside of.
const RotationCapWarp = 10000.0
RotationCapWarp is the maximum effective warp factor for body rotation animation. Above this warp, RotationTime advances at the cap rate so planet surfaces don't blur into a stripe at extreme zoom-out warp. v0.8.5.7+ — implements the v0.8.5 plan's follow-up "policy (a): clamp rotation rate at high warp" slot.
Picked at 10000× (one warp tier below max): at this rate Earth spins at ~42°/sec on screen, which is fast but trackable. Above it, sim time still advances at the user's selected warp; only the visible rotation is capped.
const ViewTiltPhiStep = 5.0
ViewTiltPhiStep is the per-press yaw nudge for the shift+←/→ keys. Unlike Theta there is no min/max — yaw is a full turn around the orbit, so the nudge wraps at 360° instead of clamping (ADR 0021 G).
Variables ¶
var ( // PlanCircularizeAtApoapsis errors. Exported so app.go's status // flash can switch on them with errors.Is. v0.9.4+. ErrNoCraftForCircularize = transferError("circularize: no active vessel") ErrCircularizeBelowAtmosphere = transferError("circularize: apoapsis below atmosphere — keep climbing") ErrCircularizeBadOrbit = transferError("circularize: hyperbolic / degenerate orbit") )
var ( ErrRendezvousNoTarget = transferError("no vessel target") ErrRendezvousDifferentPrimaries = transferError("target around a different primary") ErrRendezvousAlreadyDocked = transferError("already in DOCK READY range") ErrRendezvousNoImprovement = transferError("no useful nudge in range") ErrRendezvousNoCraft = transferError("no active vessel") // ErrRendezvousShapeMismatch / ErrRendezvousBurnTooLarge / // ErrRendezvousUnsafePeriapsis (ADR 0039 S1, #278): distinct refusal // reasons that used to all collapse into ErrRendezvousNoImprovement. // Each names the actual planner gate that fired and, where one // exists, the remedy — so "the nudge would be expensive" no longer // reads identically to "rendezvous is impossible" or "the geometry is // already optimal". Wording for the burn-too-large case is #278's own // proposed text verbatim. ErrRendezvousShapeMismatch = transferError("orbits differ in shape — circularize [C] or plan a transfer [H] first") ErrRendezvousBurnTooLarge = transferError("nudge would exceed the burn ceiling — use the transfer planner [H/I/m]") ErrRendezvousUnsafePeriapsis = transferError("nudge would drop periapsis unsafely — plan a transfer instead [H/I/m]") // ErrRendezvousNoEncounter (ADR 0039 S2, #277): the shared // phasing-coach remedy for "no real encounter to score" — both K's // inner Lambert-lookahead dead ends and Engage's own commit-search // failure return this, in the doctrine's own words, so the two // refusal chains stop pointing at each other. Before this, `w` said // "plant a nudge [K] first" and K said "no useful nudge in range" — // each pointing at the other with no way out. ErrRendezvousNoEncounter = transferError(rendezvousPhasingCoachMsg) )
Rendezvous advisory errors. Exported so app.go's status flash can switch on them via errors.Is, mirroring the PlanCircularize* family (see maneuver.go:1132). v0.10.2+.
The reasons carry no "rendezvous:" prefix (#285): the flight view labels the refusal at the display layer, exactly as it does for `circularize:` and `save:`, so a prefix here rendered twice ("rendezvous: rendezvous: no vessel target") on every K refusal.
var ( // ErrStageNoCraft — caller passed a craftIdx outside the slate. ErrStageNoCraft = errors.New("stage: no vessel at idx") // ErrStageEmpty — craft has no Stages slice (legacy literal- // constructed test fixture). The staging path requires at least // one stage entry. Real spawn paths always populate Stages via // NewFromLoadout, so this is a defensive check. ErrStageEmpty = errors.New("stage: vessel has no stages") // ErrNoSignal — the craft is an unmanned probe out of network contact // (ADR 0027); staging is a command and is blocked. canCommand also // raises the NO SIGNAL flash. ErrNoSignal = errors.New("stage: no signal (unmanned vessel out of contact)") // ErrStageOnlyOne — craft has only one stage left. Dropping // the only stage would leave the player with nothing to // control, which is the wrong default. Status-flash + no-op. ErrStageOnlyOne = errors.New("stage: cannot drop the only remaining stage") // ErrTransposeNotReady — Transpose called on a craft that is not in // the pre-transposition [Descent, Ascent, SM, CM] shape (ADR 0009). // The player must drop the three Saturn stages first. ErrTransposeNotReady = errors.New("transpose: stack not in [Descent, Ascent, SM, CM] shape — drop the launch vehicle first") )
Errors returned by StageActive.
var AllTriggerEvents = spacecraft.AllTriggerEvents
AllTriggerEvents re-exports the spacecraft-package canonical UI cycle order so existing `sim.AllTriggerEvents` references keep compiling.
var AllViewModes = [...]ViewMode{ ViewTilted, ViewTop, ViewRight, ViewBottom, ViewLeft, ViewOrbitFlat, ViewLaunch, }
AllViewModes enumerates the modes in canonical cycle order. Tilted → Top → Right → Bottom → Left → OrbitFlat → Launch — the v0.10.6+ tilt opens the cycle as the new zero-value default, the four cardinal cameras follow (each rotates 90° around the system), orbit-flat lands as punctuation, and the launch chase-cam closes the lap before wrapping. ADR 0021 D removed the conditional ViewTarget / ViewSOIPass slots — every mode is always selectable.
var LaunchSites = []LaunchSitePreset{ {Key: "Equator", Name: "Equator", LatitudeDeg: 0.0, LongitudeEastDeg: 0.0}, {Key: "KSC", Name: "Cape Canaveral (KSC LC-39A)", LatitudeDeg: DefaultLaunchpadLatitude, LongitudeEastDeg: DefaultLaunchpadLongitudeEast}, {Key: "Baikonur", Name: "Baikonur Cosmodrome", LatitudeDeg: 45.965, LongitudeEastDeg: 63.342}, {Key: "Plesetsk", Name: "Plesetsk Cosmodrome", LatitudeDeg: 62.926, LongitudeEastDeg: 40.577}, {Key: "North-Pole", Name: "North Pole", LatitudeDeg: 90.0, LongitudeEastDeg: 0.0}, }
LaunchSites is the canonical named-site list. Index 1 (KSC) is the spawn form's launchpad default, so opening the form with launchpad selected lands a Saturn V at the historical Apollo pad; Equator at index 0 is the textbook best-case baseline. KSC reuses the package DefaultLaunchpad* consts so the default has a single source.
var WarpFactors = []float64{1, 10, 100, 1000, 10000, 100000}
WarpFactors are the discrete time-warp steps per designdocs/terminal-space-program/plan.md §Time-warp UX.
Functions ¶
func CompositeEngineSummary ¶ added in v0.9.1
func CompositeEngineSummary(c *spacecraft.Spacecraft) (totalThrust, weightedIsp float64)
CompositeEngineSummary returns the pooled-engine view of a craft that may have multiple thrusting stages: sum-thrust across every stage with main thrust, mass-weighted-Isp by `Σ(Isp · thrust) / Σ thrust`. Resolves the v0.9 plan scoping decision #4 ("default: sum thrust, mass-weighted average Isp") for downstream consumers that want the composite-as-a-whole view rather than the bottom- stage view. v0.9.1+.
Returns (totalThrust=0, weightedIsp=0) when no stage has thrust (RCS-tug class composites). For single-stage craft and composites where only one stage has thrust, this returns the same numbers as reading c.Thrust + c.Isp directly — the helper degenerates correctly for the common case.
func LaunchAnchorPhi ¶ added in v0.10.7
func LaunchAnchorPhi(c *spacecraft.Spacecraft, el orbital.Elements, ok bool) (phi float64, active bool)
LaunchAnchorPhi returns the chase-plane yaw φ (radians) that aligns the craft's local-vertical with screen-up under the v0.10.6 perspective tilt, and an `active` bool that signals whether the anchor should be applied.
Caller (screens.viewBasis) passes the (el, ok) pair it already computed via activeCraftElements:
- ok == true → craft has a usable Keplerian orbit; φ is computed in the perifocal frame from R projected onto (x̂, ŷ).
- ok == false → craft is Landed or its orbit is degenerate; φ is computed in world XY from atan2(R.Y, R.X).
Both paths yield φ = (angle of R̂ in the base plane) − π/2, which is exactly the rotation that puts R̂'s base-plane projection along the yawed ŷ axis (= screen-Y after polar tilt). Math derived in the v0.10.7 grilling pass; tests in launch_anchor_test.go confirm the canvas-X component of R̂ vanishes at the returned φ.
Returns (0, false) when the craft is nil, μ is zero, or the apoapsis predicate fails (hyperbolic / degenerate / apoAlt above the launch-mission floor).
func LoadGroundStations ¶ added in v0.22.0
func LoadGroundStations() ([]GroundStationPreset, []GroundStationWarning)
LoadGroundStations reads the embedded DSN ring merged with any user overlay, dropping warnings. Call LoadGroundStationsWithWarnings to inspect them.
func LoadGroundStationsWithWarnings ¶ added in v0.22.0
func LoadGroundStationsWithWarnings() ([]GroundStationPreset, []GroundStationWarning)
LoadGroundStationsWithWarnings loads the embedded catalog merged with the user overlay ($XDG_CONFIG_HOME/terminal-space-program/ground_stations/*.json), user winning on Key. A malformed embedded file panics (build error); a malformed user file is skipped with a warning. A station with no positive range falls back to DefaultGroundStationRangeM.
func PlaceAcrossSubspaceGap ¶ added in v0.33.0
func PlaceAcrossSubspaceGap(c *spacecraft.Spacecraft, dtSeconds float64) bool
PlaceAcrossSubspaceGap Kepler-propagates a craft's primary-relative state by dt seconds — forward or backward — so a craft handed between two players' Worlds materialises where it actually is at the RECEIVER's sim-time rather than at the sender's. At LEO speed a subspace skew of one second is ~7.6 km of error, and a Parcel can sit undelivered for hours.
Same analytic step the ghost layer already uses for reports, and it carries the same honest staleness: it neither detects SOI exits nor knows about anything that would have happened to the craft in the gap. It cannot, and nothing else could either — the craft was coasting, unsimulated, on a ledger record. ok is false for a degenerate state, in which case the craft is left exactly where it was: no propagation beats a wrong one.
func RetagStackForTransfer ¶ added in v0.28.0
func RetagStackForTransfer(c *spacecraft.Spacecraft, fromOwner, toOwner string)
RetagStackForTransfer is the exported form of tagStackOwnership for the serve/relay layer that migrates a stack between player Worlds on a Transfer Control (v0.28 S5). fromOwner is the fingerprint of the World the stack is leaving; toOwner is the fingerprint of the World adopting it. After this the composite's components describe the post-transfer roles: the departing owner rides as a guest, the recipient owns its own core.
func SafeHandback ¶ added in v0.33.0
func SafeHandback(c *spacecraft.Spacecraft)
SafeHandback puts a returned craft into the known-inert configuration every handback ends in: throttle zero, main engine selected, no attitude hold, no burn of any kind running. The same invariant every time, so "undock = safe ship, set up and go" is a rule a pilot can fly by — rather than inheriting whatever the other seat happened to have dialled in for a different situation (#303: 10% throttle, RCS, Target Retrograde, on a craft the pilot had left at 100%/main/prograde).
func SeparationPush ¶ added in v0.33.0
func SeparationPush(c *spacecraft.Spacecraft)
SeparationPush nudges a returned craft clear of the stack it just left — 75 m and 0.15 m/s radially out, deliberately outside BOTH docking gates so neither proximity nor closing rate can re-fuse the pair while the pilot is still setting the safed ship up. Same magnitudes Deploy uses for the same reason; shared so the cross-player return path can't drift from the local one (#304: the cross-player path had no push at all, and the pair re-fused nine seconds after the undock).
func StackGuestOwner ¶ added in v0.33.0
func StackGuestOwner(c *spacecraft.Spacecraft) (string, bool)
StackGuestOwner names the single cross-player guest riding in a stack, if there is one — the 2-party MVP's whole cross-player membership. ok is false for a plain composite. Used by the owner-seat release, which has to know whose components it is being asked to peel.
func StackHasGuest ¶ added in v0.28.0
func StackHasGuest(c *spacecraft.Spacecraft) bool
StackHasGuest reports whether the craft is a composite carrying any cross-player (guest-owned) component — i.e. a cross-player stack rather than a plain single-World composite. Used by the transfer-control and undock-as-guest routing to tell a cross-player stack apart.
func StackMidBurn ¶ added in v0.28.0
func StackMidBurn(c *spacecraft.Spacecraft) bool
StackMidBurn reports whether the craft is actively thrusting — a planted finite burn (ActiveBurn) or a player-held manual burn (ManualBurn) in flight (v0.28 S5). Transfer Control is refused while a cross-player stack is mid-burn (ADR 0034 addendum: "refused mid-burn") so the integrator and mass-loss state don't hand off across the ownership seam.
func SystemNames ¶ added in v0.17.0
SystemNames lists the loaded systems' display names, for discovery and error messages. v0.17.
func TickCmd ¶
TickCmd returns a tea.Cmd that fires a TickMsg after d wall-time. Callers typically re-issue TickCmd from Update() to keep the loop running.
func TransposeReady ¶ added in v0.12.7
func TransposeReady(c *spacecraft.Spacecraft) bool
TransposeReady reports whether a craft is in the pre-transposition shape [Descent, Ascent, SM, CM] — the state left after the three Saturn stages decouple, where the transpose key (D) is actionable. The HUD uses it to surface the "TRANSPOSE READY — press D" hint, and Transpose uses it as its precondition.
Types ¶
type ActiveBurn ¶ added in v0.2.1
type ActiveBurn = spacecraft.ActiveBurn
Type aliases — the underlying types now live in internal/spacecraft so each Spacecraft can own its own []Nodes, ActiveBurn, ManualBurn without an import cycle. Aliases keep existing `sim.ManeuverNode` / `sim.TriggerNextPeri` references working unchanged. v0.8.1+.
type AttitudeIntent ¶ added in v0.9.3
type AttitudeIntent int
AttitudeIntent is the input intent — which SAS axis the player pressed — independent of NavMode. The TUI maps w/s/a/d/q/e to the six intents; ResolveAttitudeIntent translates intent + NavMode to the concrete BurnMode the integrator + SAS hold consume. v0.9.3+.
const ( IntentPrograde AttitudeIntent = iota IntentRetrograde IntentNormalPlus IntentNormalMinus IntentRadialOut IntentRadialIn )
type AutoWarpTarget ¶ added in v0.16.0
type AutoWarpTarget struct {
CraftID uint64
NodeID uint64
T time.Time
// Sync (v0.27 S7, ADR 0034): when true the driver chases a fixed
// sim-time — another player's subspace — instead of a node. No
// node identity, no re-freeze, no lead: arrival is AT T, at 1×,
// in the shared subspace. Every warp clamp (burn cap, SOI guard,
// node ramp, the approach term anchored at T) applies unchanged —
// planted nodes en route are lived through, not skipped.
Sync bool
SyncHandle string // whose time we're chasing (arrival chip text)
SyncOwner string // their fingerprint — the serve layer re-freezes T from their latest report (a leader at warp is a moving target)
// Rendezvous (v0.29 S1, ADR 0034 v0.29 addendum; reshaped for #252):
// when true the driver is the shared coast of a standing mutual
// rendezvous intent. T is the CURRENT waypoint's τ — held while
// coasted at, but re-frozen by driveRendezvousCoast whenever the
// waypoint advances (τ reached outside couple range) or the partner's
// earlier relayed τ is adopted. The driver releases only at the
// proximity handoff (τ reached inside couple range, where Proximity
// Co-Warp takes over) or on either player's cancel. Started by
// DriveRendezvousWarp only once both players are armed.
Rendezvous bool
RendezvousOwner string // partner fingerprint (retract detection + chip)
RendezvousHandle string // partner handle (arrival chip text)
}
AutoWarpTarget is the engaged driver's frozen aim. CraftID+NodeID is the stable identity of the burn being chased (ADR 0016 Slice 1); T is the sim-time the driver seeks before releasing to 1×. Transient — not persisted, so a save/load mid-warp lands disengaged.
type CapturePreview ¶ added in v0.8.2
type CapturePreview struct {
Primary bodies.CelestialBody
NodeIndex int // index of the arrival node in c.Nodes
When time.Time // sim-time at which the arrival fires
Approximate bool // true when only ApproachSpeed / RetrogradeCapture are populated
Inclination float64 // radians, [0, π] — exact mode only
ApoapsisM float64 // m, capture orbit apoapsis — exact mode only
PeriapsisM float64 // m — exact mode only
Hyperbolic bool // capture failed — exact mode only
Eccentricity float64 // exact mode only
// Approximate-mode fields:
ApproachSpeed float64 // |v_∞| relative to target (m/s)
RetrogradeCapture bool // craft will orbit target in retrograde sense
}
CapturePreview describes the post-arrival orbit at the last inter-primary node in the active craft's planted chain. v0.8.2.x: surfaces the capture-orbit inclination prominently so the player catches retrograde-around-target gotchas (a prograde Hohmann to Luna naturally arrives at ~110° lunar inclination, etc.) before firing.
Two modes:
- Exact: the chained predictor's rebase produced a sane state.R (well outside the target body's radius) and orbit elements reflect the post-burn capture orbit directly. ApoapsisM / PeriapsisM / Inclination / Hyperbolic are populated.
- Approximate: state.R came out ~0 (perfect-aim Hohmann, the chained propagator's static body positions miss the SOI entry geometry). Instead, the preview reports the relative approach speed (|v_∞|) and a qualitative prograde / retrograde direction inferred from v_∞ vs target's parent-frame velocity. ApoapsisM / PeriapsisM / Inclination / Hyperbolic are zero; ApproachSpeed and RetrogradeCapture are populated.
The Approximate flag distinguishes the two — HUD branches on it.
type ChatLine ¶ added in v0.32.0
type ChatLine struct {
Owner string // sender fingerprint — filtering only, never rendered
Handle string // sender handle, as rendered
Text string
// To addresses a DM at one player (fingerprint); empty = broadcast.
// ToHandle carries the target's handle so the sender's own echo can
// render the visibly-distinct "→handle:" form.
To string
ToHandle string
At time.Time // wall clock: chat expires by real seconds regardless of warp
}
ChatLine is one transient chat message (ADR 0035). Same contract as SessionEvent: serve-written, read by screens, never persisted, absent in single-player. Chat is a live co-op coordination tool — lines die with the server process and are stamped in wall clock, never sim time (players hold independent subspace times, so no sim stamp is displayable).
type CircularizePlan ¶ added in v0.9.4
type CircularizePlan struct {
DV float64 // m/s, prograde at next apoapsis
ApoAltM float64 // apoapsis altitude (m above primary mean radius) at plant time
PrimaryID string
}
CircularizePlan summarises a planted circularize-at-apoapsis node for the caller's status flash. v0.9.4+.
type Clock ¶
type Clock struct {
SimTime time.Time
// RotationTime is sim time as seen by the rotation animation.
// At warp ≤ RotationCapWarp it advances in lockstep with
// SimTime; above, it advances at RotationCapWarp × BaseStep
// per tick instead, so visible rotation stays smooth even when
// SimTime is leaping forward at warp 100000×. Lags SimTime
// while above the cap; the lag persists when warp drops back
// (a side-effect of the "freeze rotation at high warp" model
// the v0.8.5 plan called for).
RotationTime time.Time
WarpIdx int
Paused bool
BaseStep time.Duration // real-time step per tick at warp 1×
}
Clock tracks sim-time advancement and the currently active warp factor.
func (*Clock) Advance ¶
func (c *Clock) Advance()
Advance moves SimTime forward by BaseStep × warp, and RotationTime forward by BaseStep × min(warp, RotationCapWarp) — capping the visible rotation rate so planets don't blur into solid stripes at extreme warp. Called once per tick.
type CoWarpCraft ¶ added in v0.28.0
type CoWarpCraft struct {
Primary string // SOI primary ID; must match the anchor's to gate
R orbital.Vec3 // primary-relative position at the viewer's sim-time
V orbital.Vec3 // primary-relative velocity at the viewer's sim-time
}
CoWarpCraft is one peer craft placed in the anchor's frame — primary- relative position/velocity already propagated to the viewer's sim-time (the relay adapter Kepler-steps the last report across the subspace gap, exactly like a ghost). Only the SOI primary ID and the state vector are needed to gate range + |v_rel| against the viewer's active craft.
type CoWarpPeer ¶ added in v0.28.0
type CoWarpPeer struct {
Owner string
Handle string
SubspaceTime time.Time
EffWarp float64
Crafts []CoWarpCraft
// Paused distinguishes a deliberately paused partner (frozen clock)
// from one merely reporting EffWarp 0 — the rendezvous hold keys on
// it (v0.29 review). EffWarp alone can't carry this: a held viewer
// also reports 0, and treating any 0 as "paused" would deadlock both
// sides into mutual holds.
Paused bool
// Away is the peer's live nobody-at-the-controls state (#253, ADR
// 0036): their session is still simulating — a Commitment Reprieve
// holds it open — but nobody is watching. Set by the relay adapter
// from the server's per-session liveness; DriveRendezvousWarp mirrors
// the armed partner's value onto the world slate so the flight view
// carries a STANDING indication instead of only the 6 s went-quiet
// chip. False for an offline peer — that is a different thing (their
// craft stop flying), and it is the report set, not this flag, that
// goes empty then.
Away bool
// ArmedTowardViewer is set by the relay adapter when this peer has a
// live Rendezvous Warp intent aimed at the viewer (v0.29 S1, ADR 0034
// v0.29 addendum). Combined with the viewer's own RendezvousArm
// targeting this peer, the two are *mutually* armed and couple before
// the proximity gate — the second Co-Warp trigger.
//
// "Live" means a live SESSION, not merely a live report (#252 review):
// the adapter suppresses this flag (and RendezvousTau/CA) for an owner
// with no live session, so a disconnected-for-good partner's frozen
// report reads as a retract here rather than an immortal arm. A
// reprieved-away session still counts as live — its silence is held
// for, never cancelled.
ArmedTowardViewer bool
// RendezvousTau is the peer's committed encounter sim-time when
// ArmedTowardViewer — the initiator's authoritative TCA, which the
// responder adopts verbatim when it Engages back (v0.29 S1). Zero when
// the peer is not armed toward the viewer.
RendezvousTau time.Time
// RendezvousCA is the peer's committed predicted approach at Tau (m) —
// carried alongside RendezvousTau so a responder adopts the initiator's
// authoritative baseline, not its own staler recompute (v0.29 S1).
RendezvousCA float64
// RendezvousInitiator is this peer's SEAT in the mutual agreement (ADR
// 0037 §2) — true when they proposed the rendezvous and therefore fly
// the pair's clock through the terminal phase. Meaningful only
// alongside ArmedTowardViewer. Both sides relay their own bit rather
// than deriving one from the other, so a reconnect can't leave the pair
// disagreeing about who is in command; when the two bits are equal
// (both claim it, or an older peer claims neither) the rate rule
// declines to resolve a seat and the pair falls back to min-wins.
RendezvousInitiator bool
// RendezvousRate is this peer's published contribution to the pair's
// rate in the terminal phase (ADR 0037 §2): the initiator publishes
// their SELECTED warp, the copilot publishes their brake, and either
// side folds in its own burn cap first. 0 means "this seat imposes no
// ceiling" — a following copilot with nothing burning.
//
// Load-bearing that this is a selection, never a derived rate: the
// receiving side's own rate is a function of it, so relaying a
// post-clamp value back would close the #248 loop and ratchet the pair
// to 1×.
RendezvousRate float64
// RendezvousBurning marks the published rate as coming from this peer's
// active burn rather than a chosen brake, so the partner's chip can say
// "held: gern burning" instead of blaming a deliberate brake.
RendezvousBurning bool
// ActiveCraftName is the vessel this peer is flying, read off their
// report's active-craft marker (#288). The join prompt names it (#295)
// so the responder answers "gern's Relay Tug-1 wants to rendezvous",
// not just "gern" — the live wrong-vessel arm was caught from this
// seat, and only by an implausible CA. Empty when the peer's report
// carries no marker.
ActiveCraftName string
}
CoWarpPeer is one other player's co-warp contribution: their identity, subspace time, current Effective warp (for the min), and their craft in the viewer's system. Built by the relay adapter (CoWarpPeersFrom) from the store's reports — the sim-level, relay-agnostic input so the couple/decouple math + constants stay in sim (the clamp's home) while sim never imports the wire types above it.
type CoWarpResult ¶ added in v0.28.0
type CoWarpResult struct {
State CoWarpState
CoupledOwners map[string]bool
NewlyCoupled []string // handles that transitioned uncoupled→coupled
Released []string // handles that transitioned coupled→uncoupled
}
CoWarpResult is ComputeCoWarp's full output: the State to store on the World, the per-owner coupled flags to feed back as `prev` next tick (the hysteresis memory), and the couple/release transitions the reporting layer turns into chips.
type CoWarpState ¶ added in v0.28.0
CoWarpState is the transient co-warp slate the reporting layer writes onto the World each tick and clampedWarp reads: whether the anchor is coupled to anyone this tick and the min Effective warp to clamp to. MinWarp 0 with Coupled set means coupled-with-no-clamp (#248): every coupled peer is the engaged rendezvous coast's partner, whose stale reported rate is exempt from min-wins — clampedWarp guards on MinWarp > 0, so both sides derive the coast rate independently instead of deadlocking on each other's report. Never persisted; empty in single-player. Partners is the coupled handles for HUD/debug.
func (CoWarpState) WithDockCoupling ¶ added in v0.28.0
func (s CoWarpState) WithDockCoupling(ownerHandle string, ownerEffWarp float64) CoWarpState
WithDockCoupling folds a docked-as-guest coupling into the co-warp state (v0.28 S5). While a player is Docked-as-Guest their whole subspace is warp-coupled to the stack regardless of range — the guest's craft is IN the stack, not near it, so the range-gated ComputeCoWarp can't express this; instead the serve layer folds the stack owner's Effective warp in here after ComputeCoWarp. Reuses the exact S1 clamp: clampedWarp already reads CoWarp.Coupled / MinWarp, so no new clamp is needed. min-wins — the resulting MinWarp is the lesser of any range-coupled peer and the owner's warp, so the guest can always select lower (slam 1× and burn) but can't out-warp the owner. A non-positive ownerEffWarp (paused owner) imposes no coupling, matching ComputeCoWarp's paused-peer handling. A coupled state carrying MinWarp 0 (#248: an engaged rendezvous coast exempts its partner from min-wins) still adopts the owner's warp — the coast exemption is the partner's, never the stack owner's, so the guest stays clamped.
type CommDisconnectReason ¶ added in v0.32.0
type CommDisconnectReason int
CommDisconnectReason classifies why a probe has no connection this tick — the enum-plus-data shape from RendezvousWaitReason, scoped to the two reasons that are never actively wrong advice. The finer diagnosis (no antenna fitted, relay chain broken upstream) is deferred: the broken-chain case needs the BFS to report where the chain failed, a larger slice than #221 warrants.
const ( // CommDisconnectNone: connected, not a probe, or not yet classified. CommDisconnectNone CommDisconnectReason = iota // CommDisconnectBlocked: the network is in range but no unoccluded // path exists — the geometry is the problem, a relay is the fix. CommDisconnectBlocked // CommDisconnectOutOfRange: no station or relay is in link range at // all — reach is the problem, a stronger antenna is the fix. CommDisconnectOutOfRange )
type CommGraph ¶ added in v0.22.0
type CommGraph struct {
Connected map[uint64]bool
// Paths maps a connected probe's ID to its shortest relay chain as
// ordered world-frame points: the probe first, then each relay hop, then
// the terminal ground station. Absent for disconnected probes. The
// positions are the same absolute world frame the orbit canvas projects
// (body position + state), so the HUD draws segments without rebasing.
Paths map[uint64][]orbital.Vec3
// Reasons classifies each DISCONNECTED probe (#221, ADR 0027 v0.32
// amendment): the bare false collapsed three failure paths and the
// chip could only say NO SIGNAL with no hint that a relay — not a
// bigger antenna — was the answer (or vice versa). Absent for
// connected probes.
Reasons map[uint64]CommDisconnectReason
}
CommGraph is the cached per-tick connectivity result: the set of unmanned craft (by stable ID) that currently have a connection to a ground station, plus — for each connected probe — the world-frame relay chain it reaches the network through (probe, relays…, station), which the comms HUD draws and counts hops from (C2-7).
func (*CommGraph) HasConnection ¶ added in v0.22.0
HasConnection reports whether the craft with the given ID has a network connection this tick. nil-safe (a not-yet-computed graph → false).
func (*CommGraph) Path ¶ added in v0.22.0
Path returns the world-frame relay chain (probe→relays…→station) for the craft with the given ID, or nil if it has no recorded connection this tick. nil-safe.
func (*CommGraph) Reason ¶ added in v0.32.0
func (g *CommGraph) Reason(id uint64) CommDisconnectReason
Reason returns why the craft with the given ID is disconnected, or CommDisconnectNone for a connected craft / not-yet-computed graph. nil-safe.
type DockEvent ¶ added in v0.8.3
type DockEvent struct {
When time.Time
CraftIdx int // active partner's index (becomes the composite slot)
PartnerIdx int // index of the partner that was removed
CompositeName string // name of the resulting composite craft
}
DockEvent records the latest fuse for HUD-side messaging. v0.8.3+.
type DockGuestLink ¶ added in v0.28.0
type DockGuestLink struct {
OwnerFP string // stack owner's fingerprint (the docker / current holder)
OwnerHandle string // stack owner's display handle (for chips + status)
OwnerEffWarp float64 // owner's reported Effective warp — the coupling min
GuestCraftID uint64 // the guest's craft riding in the stack
// OwnerAway is the stack owner's live nobody-at-the-controls state
// (#253, ADR 0036): their session — and this player's craft riding in
// its stack — still flies under a Commitment Reprieve, but nobody is
// watching. Written by the serve layer beside the coupling fold (the
// relay ledger that rebuilds this link each tick has no session
// liveness to consult); the flight view renders it as a standing line,
// the docked-as-guest sibling of RendezvousPartnerAway.
OwnerAway bool
// OwnerActiveCraftID is the stack owner's currently-reported active
// craft ID — the ghost that IS the stack this player rides in (ADR
// 0038 S4). DockGuestCraft always fuses onto the docker's existing
// craft in place (the composite keeps the docker's identity, §"Cross-
// player docking" above), so the owner's own reported ActiveCraftID
// names the fused composite for as long as they keep flying it. Zero
// until the owner's first report lands; the rider-view camera and
// badged panels both degrade gracefully (ok=false) rather than
// tracking craft ID 0.
OwnerActiveCraftID uint64
}
DockGuestLink is the transient docked-as-guest slate the serve layer writes onto World.DockGuest each tick while one of this player's craft rides in another player's stack (v0.28 S5). It names the stack owner (for the coupling + the "docked with X" status) and carries the owner's current Effective warp so the guest's clampedWarp can fold in the min-wins coupling. GuestCraftID is the guest's own craft riding in the stack — the ID the Undock-as-guest signal hands to the owner's UndockGuest. Never persisted.
type Focus ¶ added in v0.2.0
type Focus struct {
Kind FocusKind
BodyIdx int
// GhostOwner + GhostCraftID address the spectated ghost when
// Kind == FocusGhost (v0.28 S6). Kept as a value ref, not a pointer,
// so Focus stays comparable — CycleFocus and the orbit screen's
// framing-event guard both rely on struct equality.
GhostOwner string
GhostCraftID uint64
}
Focus describes the current OrbitView center. The zero value (FocusSystem, BodyIdx=0) is the v0.1.0 behavior.
type FocusKind ¶ added in v0.2.0
type FocusKind int
FocusKind enumerates what the OrbitView canvas is centered on.
const ( // FocusSystem centers on the system primary (Sun) and auto-fits to the // outermost body's apoapsis. This is the v0.1.0 default. FocusSystem FocusKind = iota // FocusBody centers on a specific body by its index in System().Bodies. FocusBody // FocusCraft centers on the spacecraft's inertial position. Only // reachable when CraftVisibleHere is true. FocusCraft // FocusGhost centers on a remote player's ghost — the Spectate mode // (v0.28 S6, ADR 0034). Entered only from the Session screen's [v] // row action, never in the CycleFocus rotation. The camera tracks // the ghost's world position each frame; the single Framing-Event // fit frames its drawn orbit extent. Reports moving the ghost never // re-fit (ADR 0021). The ghost is addressed by (owner, craft ID) so // a stale slate degrades gracefully rather than dangling a pointer. FocusGhost )
type FrameTransition ¶ added in v0.7.6
type FrameTransition struct {
NodeIndex int // index into World.Nodes
From, To string // body IDs
When time.Time
}
FrameTransition describes an upcoming change of orbital frame — the craft (or a planted post-burn trajectory) crossing an SOI boundary into a new primary's frame. Surfaced by the HUD via World.NextFrameTransition so the player can anticipate where their integrator will hand off control.
Today's heuristic is "the first planted node whose PrimaryID differs from the craft's current primary." That catches the v0.6.3 moon → parent escape's zero-Δv arrival marker (planted in parent frame for exactly this reason) and Hohmann arrival burns (planted in the destination's frame). True trajectory-walked SOI crossings (e.g. a planned Mars flyby with no arrival burn) stay out of scope until the predictor learns to surface SOI events. v0.7.6+.
type Ghost ¶ added in v0.27.0
type Ghost struct {
Owner string // ssh key fingerprint (roster identity)
CraftID uint64 // the remote craft's stable ID (target resolution)
Handle string // display name, joined from the session roster
Name string // craft name
Glyph string // craft glyph (may be empty)
PrimaryID string // SOI primary the ghost orbits
Pos orbital.Vec3 // world-frame position at this world's sim-time
RelPos orbital.Vec3 // primary-relative position at this world's sim-time
Vel orbital.Vec3 // primary-relative velocity at this world's sim-time
}
Ghost is one remote player's craft placed at this world's sim-time (v0.27 S5, ADR 0034): the last-reported orbit evaluated analytically at the viewer's clock. Honest staleness by design — it's where they'd be if they kept coasting; a burn on their side invalidates it until the next report corrects. Pure display data: the orbit screen draws it dim with the owner's handle; physics never sees it.
type GroundStationPreset ¶ added in v0.22.0
type GroundStationPreset struct {
Key string `json:"key"`
Name string `json:"name"`
BodyID string `json:"body_id"`
LatDeg float64 `json:"lat_deg"`
LonEastDeg float64 `json:"lon_east_deg"`
AntennaRangeM float64 `json:"antenna_range_m"`
// Source is a runtime annotation ("embedded" / "user"); excluded from
// JSON so it never affects round-trips.
Source string `json:"-"`
}
GroundStationPreset is one fixed-surface comms anchor. Distinct from a LaunchSitePreset (a launchpad): a ground station is a network node. Key is the short token, BodyID the body it sits on (its surface co-rotates), LatDeg / LonEastDeg the body-fixed position (east-positive, pseudo- Greenwich at simTime=0 — same convention as launch sites), AntennaRangeM the antenna's rated range in metres (the network anchor: ground stations are long-ranged, so via combinability they extend a weak craft's reach — ADR 0027 §2 amendment).
type GroundStationWarning ¶ added in v0.22.0
GroundStationWarning records a user overlay file that failed to load — the bodies-pattern skip-bad-with-warning (ADR 0027 §5 inherits ADR 0026 §3).
func (GroundStationWarning) Error ¶ added in v0.22.0
func (w GroundStationWarning) Error() string
type HohmannPreview ¶ added in v0.2.0
type HohmannPreview struct {
TargetName string
DV1, DV2 float64 // m/s
TTransfer float64 // seconds
Valid bool // false when inputs are degenerate
Note string // human-readable reason when !Valid
// Warn (v0.10.1+) is a non-fatal advisory shown alongside a
// VALID preview: the intra-primary numbers assume a circular
// coplanar departure orbit, so an eccentric / inclined parking
// orbit makes them optimistic. Empty when the orbit is within
// tolerance. See HohmannDepartureWarning.
Warn string
}
HohmannPreview summarises a reference heliocentric (system-primary- centered) Hohmann transfer from the craft's current distance to a target body's orbital radius. The numbers assume both legs are circular and coplanar — textbook Hohmann. Phasing is ignored; this is a "what would it cost?" display, not a physically-accurate multi-impulse planner.
func (HohmannPreview) Format ¶ added in v0.2.0
func (p HohmannPreview) Format() []string
Format renders the preview as 3 short lines for the HUD.
type LaunchReleaseEvent ¶ added in v0.11.0
type LaunchReleaseEvent struct {
PrevView string
}
LaunchReleaseEvent records a ViewLaunch session ending so the App's status flash can surface a `"ORBIT READY — returning to <prev view>"` toast. Same shape as LastDockEvent — App reads and clears.
type LaunchSitePreset ¶ added in v0.17.0
type LaunchSitePreset struct {
Key string
Name string
LatitudeDeg float64
LongitudeEastDeg float64
}
LaunchSitePreset bundles a named real-world launch site with its latitude + longitude (east-positive, relative to the body's prime meridian at simTime=0 — our pseudo-Greenwich convention; see SpawnSpec.LongitudeOffset). The sites are Earth-oriented; for a launchpad on another body, give an explicit lat/lon instead.
v0.17: hoisted out of the spawn form (internal/tui/screens/spawn.go) so the form's LATITUDE cycle and the --launch-site CLI flag resolve the same set. Key is the short CLI token; Name is the display label.
func LaunchSiteByName ¶ added in v0.17.0
func LaunchSiteByName(name string) (LaunchSitePreset, bool)
LaunchSiteByName resolves a launch site by its short Key or display Name, case-insensitively. Returns ok=false when no site matches.
type ManeuverNode ¶ added in v0.2.0
type ManeuverNode = spacecraft.ManeuverNode
Type aliases — the underlying types now live in internal/spacecraft so each Spacecraft can own its own []Nodes, ActiveBurn, ManualBurn without an import cycle. Aliases keep existing `sim.ManeuverNode` / `sim.TriggerNextPeri` references working unchanged. v0.8.1+.
type ManualBurn ¶ added in v0.7.3
type ManualBurn = spacecraft.ManualBurn
Type aliases — the underlying types now live in internal/spacecraft so each Spacecraft can own its own []Nodes, ActiveBurn, ManualBurn without an import cycle. Aliases keep existing `sim.ManeuverNode` / `sim.TriggerNextPeri` references working unchanged. v0.8.1+.
type NavMode ¶ added in v0.9.3
type NavMode int
NavMode selects the reference frame the SAS axis hotkeys (prograde / retrograde / normal±, radial±) interpret against. KSP-style: the player cycles a single mode and the same six axis keys reinterpret accordingly.
NavOrbit — prograde ≡ +v̂ in the active craft's primary-relative
frame (the v0.7.3+ default).
NavSurface — prograde ≡ +(v − ω×r), velocity relative to the
rotating atmosphere; useful for ascent. Only prograde
/ retrograde rebind in this mode (KSP shows orbital
normal/radial on the surface navball too).
NavTarget — prograde ≡ unit(v_active − v_target), retrograde its
flip; radial± rebinds to BurnTarget / BurnAntiTarget
(toward / away from target). Only valid when
World.Target.Kind == TargetCraft. v0.9.3+.
const ( )
type NavballBasis ¶ added in v0.9.5
type NavballBasis struct {
NavballBasis is the orthonormal world-frame basis the navball is painted in. The ball's "lat = 0, lon = 0" point lies along EX; "lat = +90" (north pole) along EZ; "lon = +90, lat = 0" along EY (right-handed completion).
EX is always the active NavMode's prograde direction so that when the craft's nose points along prograde, the prograde marker sits at the disk centre — matching KSP's "ball rotates so prograde stays in front of you" behaviour.
EZ is the orbital normal in all modes (KSP convention: even in surface mode the navball's "north" stays orbital, since the local "up" is already covered by the radial-out marker).
v0.9.5+.
func (NavballBasis) SubObserver ¶ added in v0.9.5
func (b NavballBasis) SubObserver(dir orbital.Vec3) (latDeg, lonDeg float64)
SubObserver projects a unit world-frame direction onto the basis and returns its (lat, lon) on the navball sphere in degrees. The painter's sub-observer point is the active craft's nose direction projected this way; markers compute their position via the same transform.
The input is assumed unit-length; minor float drift is clamped.
type PredictedLeg ¶ added in v0.6.1
type PredictedLeg struct {
NodeIndex int // index into World.Nodes
State physics.StateVector // post-burn state in Primary's frame
Primary bodies.CelestialBody // frame the state is expressed in
HorizonSecs float64 // duration to predict for (until next node, or one period)
StartClock time.Time // wall-clock at which the post-burn state lives — drives time-aware body lookups in PredictedSegmentsFrom (v0.8.4+)
Samples int // adaptive trajectory-sample budget — ~96 points per orbital period the horizon spans (v0.10.3)
}
PredictedLeg describes the trajectory leg following a single planted maneuver node — the orbit the craft would fly between this node firing and the next one (or for one orbital period if there's no next node). v0.6.1 uses this to render each leg in a distinct color so the player can read which orbit segment belongs to which planted burn.
type RCSPuffSample ¶ added in v0.8.0
type RCSPuffSample struct {
Inertial orbital.Vec3
Exhaust orbital.Vec3 // unit vector — points away from craft along thrust-anti
AgeFrac float64
}
RCSPuffSample is a single recent pulse surfaced to the canvas renderer — inertial position (translated through the recorded primary's current sim-time position) plus the direction the exhaust points (anti-Δv, i.e. -dir) plus an age-fraction in [0, 1] where 0 = just fired, 1 = about to expire. Caller draws ageFrac into a fade. v0.8.0+.
type RendezvousArm ¶ added in v0.29.0
type RendezvousArm struct {
TargetOwner string // fingerprint of the partner Engaged toward
Handle string // partner display name, captured at Engage (chips/HUD never fall back to a raw fingerprint)
CraftName string // the vessel that armed — captured at Engage (#295), so a wrong-vessel arm is visible from the arming seat
Tau time.Time // the current waypoint's absolute encounter sim-time
CommittedCA float64 // m — the predicted approach at Tau, re-derived per waypoint (HUD "committed" row)
// Initiator is this side's SEAT in the agreement (ADR 0037 §2), fixed
// at invite time: the player who proposed the rendezvous is
// pilot-in-command of the pair's time, the accepter takes the copilot
// seat. Captured at Engage and relayed, so both sides agree on roles
// under reconnect. When neither side (or both) claims the seat — an
// older peer, or two crossed invites — the rate rule degrades to
// today's symmetric min-wins rather than guessing.
Initiator bool
// Approach demotes the agreement to the TERMINAL PHASE (ADR 0037 §1).
// Set at the τ handoff, where #299's release still ends the driver and
// hands the ship back at 1× — but the mutual intent survives, so the
// pair stays time-locked through the braking burns, waits, and gate
// creep of the final approach (#302). There is deliberately no distance
// tripwire: a pilot who swings 100 km wide is still rendezvousing. The
// agreement ends only on dock or an explicit cancel by either side.
Approach bool
// BrakeIdx is the COPILOT's downward-only rate selection inside the
// terminal phase (ADR 0037 §2): an index into WarpFactors, or -1 while
// following the initiator. The copilot's warp keys move it — down
// brakes the pair, up releases back toward following — and it can only
// ever lower the pair's rate, never push it. Meaningless (and left at
// its -1 "following" value) in the initiator's seat.
//
// The zero value is 0, i.e. a 1× brake, which is wrong for a fresh
// arm — EngageRendezvousWarpAs stamps -1 explicitly, and the τ handoff
// re-stamps it, so no arm ever reaches the terminal phase with an
// unintended brake.
BrakeIdx int
// PrevCommittedCA / PrevCommittedCASet (ADR 0039 S3, #281): the CA at
// the waypoint immediately before the current one, captured by
// resolveRendezvousWaypoint just before it overwrites CommittedCA on
// an advance. Feeds the RENDEZVOUS chip's trend row — "did the last
// waypoint get better or worse" — a question the degrade watchdog
// above can't answer, because its own baseline re-bases with the
// drift it's supposed to catch (see degradeBaseCA's doc comment): a
// standing intent that worsens by a little every waypoint never trips
// it, since each re-derivation re-commits a fresh (worse) baseline.
// Unset before the first waypoint advance, so the chip stays silent
// until there are two committed CAs to compare — a single point has
// no trend.
PrevCommittedCA float64
PrevCommittedCASet bool
// contains filtered or unexported fields
}
RendezvousArm is the viewer's outgoing Rendezvous Warp intent (v0.29 S1, ADR 0034 v0.29 addendum): the partner they have Engaged toward, plus the CURRENT waypoint — encounter sim-time and predicted approach. Since #252 the arm is a STANDING mutual intent ("we are rendezvousing"), not a commitment to one encounter: reaching Tau outside couple range advances the waypoint (Tau/CommittedCA are re-derived and the coast continues) rather than clearing the arm, so the range-free coupling and rate-lock span the whole multi-maneuver approach. The arm ends exactly two ways by intent: an explicit cancel by either player, or the proximity handoff when a waypoint arrives inside couple range and Proximity Co-Warp takes over.
Lifetime requires a live partner SESSION (#252 review, finding 1). With the arm unbounded in time, "the partner is still in this" has to mean their session exists — attended or reprieved-away — not that a report of theirs exists: the relay store never scrubs reports, so a partner who disconnects for good leaves a frozen report whose intent bit would otherwise hold this arm (and its 0×-hold or dead-orbit coast) forever. The serve layer enforces it at the peer seam (relay.CoWarpPeersFrom's liveness input): a dead session's relayed arm is suppressed, which the coast reads as a genuine retract — so a partner disconnect releases the arm through the normal cancel path, not through any wire message the departed session never got to send. Transient like AutoWarp/CoWarp — never persisted.
type RendezvousArrival ¶ added in v0.29.0
type RendezvousArrival struct {
Handle string // the partner whose encounter we arrived at
Owner string // their fingerprint
}
RendezvousArrival marks a completed Rendezvous Warp (v0.29 S1) — set by driveRendezvousCoast at the proximity handoff (the coast reached a waypoint inside couple range, #252), consumed (and cleared) by the serve wrapper to fire the arrival chip. Transient, like SyncArrival.
type RendezvousInvite ¶ added in v0.29.0
type RendezvousInvite struct {
Owner string // partner fingerprint — EngageRendezvousWarp's target on respond
Handle string // display name for the prompt/chip
CraftName string // the vessel the initiator armed (#295) — empty when their report carries no marker
Tau time.Time // the initiator's committed encounter sim-time
CA float64 // m — the initiator's committed predicted approach
// Blocked marks an invite from a subspace-diverged peer (#250): the
// intent is live, but the coast could never start across the gap, so
// the prompt renders as a non-joinable attribution ([y] suppressed,
// the direction-correct Sync named as the way in) instead of silently
// vanishing. AheadBy is the signed viewer-minus-initiator subspace
// offset (positive: the viewer is ahead — Sync is forward-only, so
// then the initiator is the one who must Sync). Both zero while
// joinable.
Blocked bool
AheadBy time.Duration
}
RendezvousInvite is a peer's live Rendezvous Warp intent aimed at the viewer, awaiting a response (v0.29 S2): who, and the committed τ + predicted approach the responder adopts verbatim on join. The World slate field of the same name carries at most one (pairwise MVP).
type RendezvousRateHolder ¶ added in v0.33.0
type RendezvousRateHolder int
RendezvousRateHolder classifies what is setting the pair's rate — the answer to "why do my warp keys do nothing", which was the exact 30 minutes of confusion behind #305.
const ( RendezvousRateNone RendezvousRateHolder = iota // RendezvousRateYou: your own selection is the pair's rate. RendezvousRateYou // RendezvousRateFollowing: you are the copilot, following the // initiator's clock. RendezvousRateFollowing // RendezvousRatePartnerBraking: the copilot has braked the pair below // what the initiator selected. RendezvousRatePartnerBraking // RendezvousRatePartnerBurning: the partner is burning, so the pair is // held at the burn cap. RendezvousRatePartnerBurning // RendezvousRatePartnerPaused: the partner's clock is stopped — the // deepest brake there is. RendezvousRatePartnerPaused )
type RendezvousRateState ¶ added in v0.33.0
type RendezvousRateState struct {
Seat RendezvousSeat
Handle string // partner's display name, for the chip's "held:" row
// PartnerRate is the ceiling the partner's seat imposes (0 = none):
// the initiator's selected warp, or the copilot's brake, either way
// already folded with their own burn cap. A paused partner is carried
// as a 1× crawl rather than "no ceiling" — see refreshRendezvousRate.
PartnerRate float64
PartnerBurning bool
PartnerPaused bool
}
RendezvousRateState is the terminal phase's standing rate slate: the viewer's seat plus everything the PARTNER contributes to the pair's rate. Written each tick by DriveRendezvousWarp, read by clampedWarp and by the RENDEZVOUS chip. Transient like the rest of the rendezvous slate; the zero value means "no seated agreement", which is every solo tick.
Deliberately holds only the partner's half. The viewer's own selection, brake and burn cap are read LIVE in clampedWarp and in RendezvousRateHold — a slate refreshed on the serve pass is a tick stale, and a warp key whose effect waits a tick for a relayed slate is the unresponsive-keys complaint all over again.
type RendezvousSeat ¶ added in v0.33.0
type RendezvousSeat int
RendezvousSeat is the viewer's role in a resolved terminal-phase agreement.
const ( // RendezvousSeatNone: no agreement in the terminal phase, no matched // partner, or seats that don't resolve to exactly one initiator. RendezvousSeatNone RendezvousSeat = iota // RendezvousSeatPilot: the viewer proposed the rendezvous and flies the // pair's clock. RendezvousSeatPilot // RendezvousSeatCopilot: the viewer joined; their warp keys brake the // pair or release back to following. RendezvousSeatCopilot )
type RendezvousWait ¶ added in v0.31.1
type RendezvousWait struct {
Reason RendezvousWaitReason
AheadBy time.Duration // gap direction/magnitude; zero unless Reason is SubspaceGap
}
RendezvousWait is the classified armed-but-not-coasting slate (#250): the reason the coast has not started, plus the signed viewer-minus- partner subspace offset when that reason is a gap (positive: the viewer warped ahead). Zero value when idle or coasting.
type RendezvousWaitReason ¶ added in v0.31.1
type RendezvousWaitReason int
RendezvousWaitReason classifies why an armed Rendezvous Warp has not started coasting (#250). Deliberately an enum-plus-data shape rather than a bag of bools — #221's CommGraph work wants the same "classify the reason and say it" pattern, so the vocabulary should converge.
const ( // RendezvousWaitNone: no arm held, or the shared coast is running. RendezvousWaitNone RendezvousWaitReason = iota // RendezvousWaitPartner: genuinely waiting — the partner has not // Engaged back (or has no report in this tick's peer set). RendezvousWaitPartner // RendezvousWaitSubspaceGap: the pair has diverged past // CoWarpSubspaceTolerance, so the coast cannot start no matter what // the partner does — Sync is the only way back. RendezvousWaitSubspaceGap // RendezvousWaitSelf: the partner HAS armed back, but the viewer's // own non-rendezvous Auto-Warp (a Sync or node-chase) is engaged — // driveRendezvousCoast defers the coast start rather than clobber it // (#260), so the wait is the viewer's own doing, not the partner's. RendezvousWaitSelf )
type RendezvousWaypoint ¶ added in v0.31.1
type RendezvousWaypoint struct {
Handle string // the partner the intent is held with
Owner string // their fingerprint
}
RendezvousWaypoint marks a passed waypoint on a standing rendezvous intent (#252) — set by driveRendezvousCoast when the coast reaches the committed τ outside couple range and advances to a newly derived encounter. Consumed (and cleared) by the serve wrapper to fire the waypoint chip: an advance must be visible (a silent one reads as the coast being broken), but it is neither an arrival nor a cancel.
type SOIPass ¶ added in v0.18.0
type SOIPass struct {
Body bodies.CelestialBody // body whose SOI the live path crosses
PeriluneRadius float64 // distance to Body centre at closest approach (m)
TimeToPerilune float64 // seconds from now to perilune
Impact bool // perilune radius is below the Body surface
PeriluneRel orbital.Vec3 // body-relative offset of perilune from Body centre (Local-to-Body, ADR 0021 B)
HasPerilunePt bool // false when the arc couldn't place the marker point
EntryRel orbital.Vec3 // body-relative offset of the arc's SOI-entry ring crossing (ADR 0021 C)
ExitRel orbital.Vec3 // body-relative offset of the SOI-exit ring crossing
HasEntry bool // false when the arc's first sample isn't on the SOI Ring
HasExit bool // false when the arc never exits — impact, horizon-truncated, node-capped
TimeToEntry float64 // seconds from now to SOI entry (the SOI PASS chip's T-entry readout)
HasEntryTime bool // false when the predictor reported no entry transition for the Body
ArcSegments []SOISegment // foreign-SOI arc (PrimaryID == Body.ID); draw via SegmentDrawPoints
OnwardSegments []SOISegment // in-SOI residence pass only (#157): the post-exit continuation (parent / heliocentric legs); nil for sibling passes
}
SOIPass is the predicted transit of the live, *unburned* trajectory through a sibling Body's sphere of influence (ADR 0019). It is computed always-on from the active craft's live state and is independent of the Target slot — KSP shows the encounter whether or not the body is targeted, and so do we.
The in-SOI residence variant (#157) reuses the same shape after SOI entry: while the craft sits inside a non-root Body's SOI on a trajectory that leaves it, Body is the craft's *current* primary, HasEntry is false (the crossing is in the past), and OnwardSegments carries the post-exit continuation — so the ring/arc/marker pipeline keeps drawing through the transit instead of switching off at the boundary.
func (SOIPass) PeriluneAltitude ¶ added in v0.18.0
PeriluneAltitude is the perilune radius above the Body's surface; negative means the trajectory impacts.
type SOISegment ¶ added in v0.2.0
type SOISegment struct {
PrimaryID string
Points []orbital.Vec3 // inertial, system-primary-centered, at each sample's clock
RelPoints []orbital.Vec3 // offset from the owning primary's center at each sample's clock
// EntryEl is the body-relative two-body conic at the SOI-crossing that
// opened this segment (set only on segments born from a rebase, not the
// starting frame), with EntryClock the refined crossing wall-clock. They
// let a foreign-SOI segment be redrawn analytically — DensifyForeignArcs —
// so a sharp planted-leg perilune draws as a smooth curve instead of
// equal-time integrated facets (ADR 0023 D). HasEntryConic gates them.
EntryEl orbital.Elements
EntryClock time.Time
HasEntryConic bool
}
SOISegment is a contiguous run of predicted-trajectory samples that share the same owning SOI primary. PrimaryID == craft's home primary means "still in the home SOI"; a different ID means the segment has crossed into another body's sphere of influence.
Each sample is recorded twice: at its inertial (system-primary- centered) position, and as the body-relative offset from the owning primary's center at that sample's clock. The offsets are the Local-to-Body Arc's raw material (ADR 0021 B): a foreign-SOI segment draws them anchored at the body's CURRENT position (SegmentDrawPoints), because at the inertial sample positions the body's own motion smears the in-SOI hyperbola across many times the SOI (~24×, measured Kern→Cursor) and the encounter reads as a straight line.
type SessionEvent ¶ added in v0.27.0
type SessionEvent struct {
Kind SessionEventKind
Owner string
Handle string
At time.Time
// To addresses an event at one player (fingerprint): a Sync event
// is only meaningful to the player whose subspace was joined.
// Empty means broadcast (join/leave).
To string
// Detail is extra display context for events that need it — ADR 0036
// uses it for which Commitment is holding an away session up
// ("rendezvous" / "dock"), so the partner learns what is at stake
// rather than only that someone went quiet.
Detail string
// Elapsed is how much sim-time ran unattended, carried by
// SessionEventResumed (ADR 0036 S6). A player who reconnects after
// hours away lands in a world whose clock jumped; this is the number
// that accounts for it. Zero on every other kind.
Elapsed time.Duration
}
SessionEvent is a transient session moment (join / leave / sync — the v0.13 chip vocabulary). At is wall clock: chips expire by real seconds regardless of warp. Owner (fingerprint) is never rendered — the serve layer uses it to keep your own join out of your chips.
type SessionEventKind ¶ added in v0.27.0
type SessionEventKind int
SessionEventKind enumerates the moments the chip stack surfaces.
const ( SessionEventJoin SessionEventKind = iota SessionEventLeave SessionEventSync // someone arrived at your subspace ("X synced to you") SessionEventSyncedTo // you arrived at theirs ("synced to X") — local only, never broadcast SessionEventCoWarpCoupled // co-warp coupled with a nearby player (v0.28 S1) — local only SessionEventCoWarpReleased // co-warp released on separation (v0.28 S1) — local only SessionEventDocked // cross-player dock fused ("docked with X", v0.28 S5) SessionEventUndocked // cross-player stack split ("undocked from X", v0.28 S5) SessionEventTransfer // stack control handed over ("control → X", v0.28 S5) // SessionEventUndockRefused: my undock-as-guest was refused on the stack // owner's side — my components are no longer the top of the stack, so // peeling them would swap the two players' vehicles (#307). Addressed at // the guest, who pressed the key and is still docked. SessionEventUndockRefused // SessionEventDockLost: the cross-player stack this dock named no longer // exists (#309) — the craft riding in it went with it. Addressed at the // guest, whose docked-as-guest marker would otherwise just vanish. SessionEventDockLost // SessionEventTransferRefused: my [J] was refused before anything moved // (ADR 0040 §2) — most often because the partner has no live session, so // there is nobody there to take the stick. Detail carries the reason // verbatim; addressed at the player who pressed the key. SessionEventTransferRefused // SessionEventParcelReturned: a craft the owner released while I was away // arrived with me on connect (ADR 0040 §3). Distinct from // SessionEventUndocked because I did not ask for it and was not there — // the chip has to account for a ship appearing on my slate. SessionEventParcelReturned // SessionEventReleaseRefused: my owner-seat release could not peel the // guest's components because they sit under my own — the post-transfer // configuration (#314, ADR 0040 §5). Addressed at the owner who pressed // the key; the way out is to hand control back. SessionEventReleaseRefused // SessionEventControlReclaimed: the stack I owned was taken back from my // empty seat by the guest riding in it (ADR 0040 §4). Addressed at the // returning owner, who would otherwise find their vehicle simply gone. SessionEventControlReclaimed // Rendezvous Warp moments (v0.29 S2) — all local-only: each side's // serve wrapper derives them from its own World transitions. SessionEventRendezvousArmed // a partner armed toward you ("X wants to rendezvous") SessionEventRendezvousArrived // a waypoint arrived inside couple range — the proximity handoff ("rendezvous with X — encounter reached") SessionEventRendezvousCancelled // the arm/coast was released by a cancel/retract ("rendezvous with X cancelled") SessionEventRendezvousDegraded // the held encounter slipped past the committed approach SessionEventRendezvousWaypoint // a waypoint passed outside couple range — the standing intent advanced (#252) // SessionEventServerRestart announces an admin-triggered graceful // restart to every connected player before the listener drains // (v0.30 S4) — a warning, not a silent drop; progress persists and a // reconnect resumes. SessionEventServerRestart // Reprieve moments (ADR 0036), addressed at the player holding a // Commitment with the one who went silent — the person whose own // flight now depends on an empty chair. SessionEventWentQuiet // their peer stopped answering; the Commitment holds the session up SessionEventBack // they are answering again (woken, or reconnected and displaced) SessionEventTimedOut // the session ended while away — nobody ever came back // SessionEventResumed opens the replay of everything that happened // while this player's own session ran unattended — local only, and // the only event carrying Elapsed. SessionEventResumed )
type SessionInfo ¶ added in v0.27.0
type SessionInfo struct {
IsHost bool // viewer is the session's root host (promote/demote, stop-hosting)
// CanAdminister is true for the host and any promoted admin (v0.30
// S2): the invite pane and mint/revoke are gated on this, not on
// IsHost. Authorization is still enforced in the serve handler — this
// only drives what the screen offers.
CanAdminister bool
Self string // viewer's fingerprint — the screen marks "you"
Players []SessionPlayer
Invites []SessionInvite // populated for the host and admins
// Version surface (v0.30 S5). RunningVersion is always set on a
// server; AvailableVersion is the newest published release when one is
// newer than running (else ""); AdoptCapable is whether the supervisor
// signalled adopt-capability — only then is the [u] restart-to-adopt
// affordance offered (else the screen points at the manual update
// path). The readout is universal; only the adopt action is gated.
RunningVersion string
AvailableVersion string
AdoptCapable bool
}
SessionInfo is the Session screen's whole slate.
type SessionInvite ¶ added in v0.27.0
SessionInvite is one outstanding invite code (host's screen only).
type SessionPlayer ¶ added in v0.27.0
type SessionPlayer struct {
Fingerprint string
Handle string
Role string // sessiondir.RoleHost / RoleGuest (plain strings here to keep sim below the store)
Online bool
// Last-known flight state, from the session store. Zero values
// mean "no report yet" (offline since before this server run).
System string
Primary string
CraftCount int
// DeltaT is their subspace time minus the viewer's — positive
// means they're ahead. Meaningless (and false) when HasReport is
// false.
HasReport bool
DeltaT time.Duration
// DockedGuest marks a player riding someone's stack. Inert until
// the v0.28 "touch" cycle ships cross-player docking.
DockedGuest bool
// Away marks a player whose session is still simulating but who has
// gone silent (ADR 0036). Distinct from Online being false, which
// means the session is gone: an Away player's craft keep flying, keep
// holding the frontier, and keep whatever Commitment earned them a
// Reprieve — there is simply nobody at the controls.
Away bool
// RangeM is the live range from the viewer's active craft to this
// player's nearest craft in the same SOI (ADR 0037 §5) — the column
// that makes the 35 km warp-lock neighbourhood learnable by watching a
// number close, rather than by crossing an invisible line. HasRange is
// false when there is nothing to measure (no anchor, no same-primary
// craft, no report), and the roster then renders a blank instead of a
// zero distance.
HasRange bool
RangeM float64
// WantsRendezvous / RendezvousOut are the roster-row Rendezvous Warp
// markers (v0.29 S2): this player is armed toward the viewer awaiting
// a response / the viewer is armed toward this player. Both render as
// row tags on the Session screen.
WantsRendezvous bool
RendezvousOut bool
}
SessionPlayer is one roster row as the viewer sees it.
type SpawnSpec ¶ added in v0.8.2
type SpawnSpec struct {
LoadoutID string
ParentBodyID string
AltitudeM float64
Retrograde bool
// Inclination (v0.17+) tilts the spawned circular orbit off the
// parent's equator by this many degrees, measured in the body-
// equatorial frame. Zero (the common case) spawns equatorial,
// byte-identical to the pre-v0.17 placement. The orbit stays
// circular; only the plane tilts (ascending node along body-frame
// +Y, the spawn-position axis). Used by the --inclination CLI flag.
Inclination float64
Alongside bool
// DesignID (v0.24 / ADR 0029) names a saved VAB design to spawn. When
// set it takes precedence over LoadoutID / CustomStages: SpawnCraft loads
// the design from the app-managed store, resolves it against the live
// catalog (composed parts aggregated, atomic refs resolved, decouple +
// nose-payload plans carried), and builds the craft — so a design flies
// identically to an equivalent catalog loadout. Placement is orthogonal.
DesignID string
// CustomStages (v0.10.1+) is a player-assembled stage list from
// the spawn-form stack configurator, bottom-first (same
// convention as Loadout.Stages). When non-empty it builds the
// craft via spacecraft.NewFromStages and LoadoutID is ignored —
// a custom stack has no catalog archetype. Empty (the common
// case) → the LoadoutID path. Placement (orbit / launchpad /
// alongside) is orthogonal and still applies.
CustomStages []spacecraft.Stage
// NosePayloadPlan (v0.14 / ADR 0011) is the top-release counterpart
// to a Loadout's bottom-up DecouplePlan: a list of how many
// contiguous TOP stages of CustomStages form a docked nose payload
// (released by Undock, not Staging) rather than linear firing-core
// stages. v0.14 honours a single entry — one nose payload, itself
// possibly multi-stage (the Apollo LM = [Descent, Ascent]). When set
// (and CustomStages is non-empty), SpawnCraft splits the stack at the
// seam, builds the core and payload, and assembles them into a ready
// docked composite — so a CSM+LM spawns already in the
// post-transposition shape. Nil/empty ⇒ a plain linear custom craft.
NosePayloadPlan []int
// Launchpad (v0.9.2+): when true, spawn at altitude 0 on the
// parent body's surface co-moving with the rotating ground at
// `Latitude` / `LongitudeOffset` (degrees, north / east positive).
// Velocity is ω × r (surface co-rotation), so a craft on the
// pad sits stationary relative to the ground and feels the
// ~465 m/s eastward boost at the equator from Earth's spin.
// Overrides AltitudeM + Retrograde + Alongside; ParentBodyID
// still selects which body's surface (default: active craft's
// current primary).
Launchpad bool
// Latitude is the surface latitude in degrees north positive.
// Sub-zero values pick southern hemisphere; |Latitude| > 90 is
// clamped. v0.9.2+.
Latitude float64
// LongitudeOffset (v0.9.2+) is the surface longitude offset in
// degrees east relative to the body's prime meridian at
// simTime=0 — our pseudo-Greenwich convention. A value of
// -80.604 places the pad at Cape Canaveral's Earth-relative
// longitude. Without this offset the spawn longitude depends
// only on sim time (the body's rotation phase), so consecutive
// launches at different sim times spawn at different points.
// With it, "Cape Canaveral" lands at Cape Canaveral regardless
// of sim time. The Landed bypass continues to rotate the pad
// with the body once spawned.
LongitudeOffset float64
}
SpawnSpec describes a craft to spawn. v0.8.2 ships these axes:
- LoadoutID: propulsion archetype. Empty → round-robin via nextLoadoutID().
- ParentBodyID: which body to orbit. Empty → active craft's current primary.
- AltitudeM: altitude above the parent's mean radius (m). Zero → 500 km default.
- Retrograde: spawn going retrograde rather than prograde.
- Alongside: spawn within the docking gate of the active craft, matching its velocity. Overrides ParentBodyID + AltitudeM + Retrograde. (v0.8.3+ — for docking testing.)
Future patches may add inclination, a phase-angle offset, etc.
type StartScenario ¶ added in v0.17.0
type StartScenario struct {
// SystemName selects the star system by System.Name (case-insensitive).
// Empty → Sol.
SystemName string
// BodyID is the parent body to spawn at (ID or English name, resolved
// via System.FindBody). Empty → the system's home planet (Earth in Sol,
// else the first planet).
BodyID string
// Loadout is the craft loadout ID (e.g. "Saturn-V"). Empty → S-IVB-1.
Loadout string
// Surface selects a launchpad spawn (LatDeg/LonDeg) over an orbital one
// (AltitudeM/InclDeg/Retrograde).
Surface bool
// Orbital placement.
AltitudeM float64
InclDeg float64
Retrograde bool
// Surface placement (degrees north / degrees east of pseudo-Greenwich).
LatDeg float64
LonDeg float64
}
StartScenario is a resolved request to configure a fresh world's starting craft from outside the TUI — built by the command-line flags in cmd/terminal-space-program and applied via ApplyStartScenario. It only ever shapes a fresh NewWorld (startup never auto-loads a save), so there's no persisted-state interaction. v0.17.
type SyncArrival ¶ added in v0.27.0
type SyncArrival struct {
Handle string // whose subspace we arrived in
Owner string // their fingerprint — addresses the "synced to you" chip
}
SyncArrival marks a completed Sync (v0.27 S7) — set by resolveAutoWarp at release, consumed (and cleared) by the serve wrapper to fire the arrival chips on both sides. Transient.
type Target ¶ added in v0.9.0
type Target = spacecraft.Target
Target / TargetKind moved to the `spacecraft` package in v0.9.3 polish so each Spacecraft can carry its own per-craft target as a struct field (per-craft target binding — each vessel remembers its own target across active-craft switches). The aliases below preserve the existing API surface so readers like `w.Target.Kind == sim.TargetCraft` continue to compile unchanged.
type TargetApproach ¶ added in v0.17.3
type TargetApproach struct {
Dist float64
TCA float64
EntersSOI bool
// PeriluneRel is the analytic closest-approach point relative to the
// target's centre (rp along the periapsis direction), in the
// target-relative inertial frame. Set only when the SOI-entering
// hyperbola resolved a periapsis direction; HasPeriluneRel gates it.
PeriluneRel orbital.Vec3
HasPeriluneRel bool
// RelEl is the full body-relative encounter conic (the hyperbola the
// perilune rides), so a caller can redraw the SOI-pass arc analytically
// from it rather than from the sparse integrated samples (ADR 0023 D).
// Set alongside PeriluneRel; HasRelEl gates it.
RelEl orbital.Elements
HasRelEl bool
}
TargetApproach summarises a craft's predicted closest approach to a body target. Dist is the distance to the target's centre (perilune radius when the trajectory enters the target's SOI, else the primary-frame miss distance); TCA is seconds from now to that approach; EntersSOI reports whether the path crosses the SOI (so a caller can show perilune altitude vs. a flyby miss).
type TargetKind ¶ added in v0.9.0
type TargetKind = spacecraft.TargetKind
Target / TargetKind moved to the `spacecraft` package in v0.9.3 polish so each Spacecraft can carry its own per-craft target as a struct field (per-craft target binding — each vessel remembers its own target across active-craft switches). The aliases below preserve the existing API surface so readers like `w.Target.Kind == sim.TargetCraft` continue to compile unchanged.
type TickMsg ¶
TickMsg is emitted by the Bubble Tea runtime on each physics step. The embedded time.Time is the wall-clock time of the tick, unused by the physics integrator but handy for throttling UI updates.
type TrailPoint ¶ added in v0.11.0
TrailPoint is one sample in the LaunchTrail FIFO. Stored in body-fixed coordinates so the renderer re-projects via render.BodyFixedToWorld at current sim-time — the geographic launch site stays geographic as the body rotates underneath the inertial frame.
type TransferComparison ¶ added in v0.12.1
type TransferComparison struct {
CombinedDv float64 // total Δv of the combined fused-Lambert transfer (+Inf if non-convergent)
SplitDv float64 // total Δv of the split raise + plane-change + capture
Strategy string // "combined" | "split"; "" when not an intra-primary plant
}
TransferComparison is the dual-strategy Δv breakdown for an intra- primary [H] auto-plant: the combined fused-Lambert transfer (plane change folded into the departure) vs the split (coplanar raise + a plane change at the slow transfer apoapsis). PlanTransfer plants the cheaper and records this for the HUD. v0.12.x+ (ADR 0005).
func (TransferComparison) Format ¶ added in v0.12.1
func (tc TransferComparison) Format() string
Format renders the dual-strategy comparison as a one-line HUD flash — both candidate Δv totals and which was planted. Empty when the last plant wasn't an intra-primary transfer. v0.12.x+.
type TransferOptions ¶ added in v0.10.5
PorkchopGrid computes a launch-window grid for a Hohmann-style transfer to the target body. Axes: depDays (offsets from now) and tofDays (time of flight). Each cell = total Δv (departure + capture, m/s); NaN for cells where Lambert didn't converge.
Uses the same parking-orbit and capture-orbit defaults as PlanTransfer (craft's current |r| at departure, 200 km altitude at destination).
v0.5.7: rejects same-primary targets (moon of craft's primary) with errSamePrimaryUseHohmann — the heliocentric Lambert math doesn't model in-SOI transfers. The porkchop screen surfaces the error as a "use [P] for Hohmann" banner. TransferOptions bundles the per-cell Lambert solve parameters that porkchop / PlanTransferAt forward to the planner: prograde-vs- retrograde, revolution count, and short-vs-long branch selection. Zero value (NRev=0, Retrograde=false, LongBranch=false) is the legacy single-rev prograde short-branch path. v0.10.5+.
type TriggerEvent ¶ added in v0.6.0
type TriggerEvent = spacecraft.TriggerEvent
Type aliases — the underlying types now live in internal/spacecraft so each Spacecraft can own its own []Nodes, ActiveBurn, ManualBurn without an import cycle. Aliases keep existing `sim.ManeuverNode` / `sim.TriggerNextPeri` references working unchanged. v0.8.1+.
type ViewMode ¶ added in v0.6.4
type ViewMode int
ViewMode selects the canvas projection — which world axes map to canvas X+ and Y+. v0.6.4+. World-level state so the orbit screen and the maneuver-planner mini-canvas share the same camera angle without per-screen coordination.
Projections only, per the Camera Contract (ADR 0021): a ViewMode never picks the camera's center or zoom — Focus picks *what* the camera centres on, ViewMode picks *which projection*. Seven modes: the v0.10.6 perspective-tilt view (the zero-value default), four hard-coded cardinal views (Top, Right, Bottom, Left), the orbit-flat view that projects onto the active craft's orbit plane regardless of inclination, and the launch chase-cam. The v0.17.3 ViewTarget and v0.18.0 ViewSOIPass auto-framing views are retired (ADR 0021 D) — reading an encounter is "focus the pass Body", and the Local-to-Body arc draws the capture curve there.
const ( // ViewTilted (v0.10.6+) renders the active craft's perifocal basis // with a polar tilt θ + yaw φ (sourced from World.ViewTilt). When // the active craft has no valid orbit (Landed / hyperbolic / // degenerate / no craft) the basis falls back to a tilted world- // axis basis so the depth cue stays alive on the pad. Prepended // to the cycle so it is the iota zero-value — a freshly spawned // World opens here, replacing pre-v0.10.6's ViewTop default. No // save migration: ViewMode is a per-session UI preference, not // persisted (world.go:60-70). ViewTilted ViewMode = iota // ViewTop is the pre-v0.6.4 default: drop world Z, project onto // world (X, Y). Equatorial orbits read as ellipses; inclined // orbits foreshorten. ViewTop // ViewRight looks at the system from world +X toward origin: // canvas X+ = world Y+, canvas Y+ = world Z+. An equatorial // orbit appears edge-on as a horizontal line passing through // the body's silhouette — useful for "watch the craft swing // around the back of the planet" geometry that Top hides. ViewRight // ViewBottom mirrors ViewTop vertically — looking up from -Z. // Same world-axes projection as Top with canvas Y inverted, so // the same orbit reads with N / S flipped. Useful when the // player wants the moon "below" the apsidal line for spatial // orientation. ViewBottom // ViewLeft mirrors ViewRight horizontally — looking from -X // toward origin. Canvas X+ = world Y-, canvas Y+ = world Z+. ViewLeft // ViewOrbitFlat projects onto the active craft's orbit plane // via the perifocal (x̂, ŷ) basis. Inclined orbits render as // clean ellipses with no foreshortening — the geometry the // other views can't reveal because they're tied to world axes. // Falls back to ViewTop's basis when the orbit is degenerate // (no craft, e ≥ 1, a ≤ 0). Useful for reading the orbit's // actual shape as if i = 0. ViewOrbitFlat // ViewLaunch (v0.11.0+) is the chase-cam launch scene — a // human-scale side view with the rocket centred, the horizon // curving below in Body.SurfaceColor, and a body-fixed pad // marker + breadcrumb trail. Routed into automatically on // active-slot Landed-false→true transitions (a named Camera // Contract carve-out — it answers the player's launch command); // left via a manual `v` cycle. ADR 0021 D retired the old // apoapsis-floor auto-release — no ambient sim state moves the // camera. Appended to the cycle (NOT prepended) so ViewTilted // stays the zero-value default. ADR-0002 captures the rationale // for shipping this as a distinct ViewMode instead of extending // ViewTilted. ViewLaunch )
type ViewTilt ¶ added in v0.10.6
ViewTilt holds the polar tilt θ and yaw φ (degrees) that ViewTilted applies to the projection basis. v0.10.6+. Per-session UI state — not persisted to save (same convention as ViewMode and InstantSAS). Theta is player-tunable via shift+up / shift+down at the orbit screen; Phi is player-tunable via { / } (ADR 0021 G, completing the "adjust angles" half of the KSP map-view intent).
func DefaultViewTilt ¶ added in v0.10.6
func DefaultViewTilt() ViewTilt
DefaultViewTilt returns the starting (Theta, Phi) for a freshly constructed World. 25° polar tilt, 0° yaw — KSP defaults to ~30° but the terminal canvas's 2:4 braille aspect makes foreshortening read stronger than a graphical UI, so a touch less keeps inclined orbits from looking squashed. Tune in flight (shift+up / shift+down).
type World ¶
type World struct {
Systems []bodies.System
SystemIdx int
Calculator orbital.Calculator
Clock *Clock
// Crafts is the slate of player vessels. v0.8.1+: replaces the
// pre-v0.8.1 single-pointer `Craft` field. Empty when no primary
// is loaded; otherwise has at least one entry. ActiveCraftIdx is
// the index of the currently-controlled craft (the one the HUD,
// manual flight, and node planter all bind to). Cycled via the
// `[` / `]` keys.
//
// All historical call sites that read `w.ActiveCraft()` go through the
// `ActiveCraft()` accessor below — there's no longer a "the"
// craft, only "the active one."
Crafts []*spacecraft.Spacecraft
ActiveCraftIdx int
// NextCraftID is the monotonic source of stable Spacecraft.IDs
// (v0.14.x / ADR 0012). Stamped onto each craft as it enters the
// slate; never decremented or reused, so a target bound to an ID
// can never alias a different vessel after a slate mutation (GH #87).
// Persisted (save schema v7). Zero is pre-stamp; EnsureCraftIDs
// initialises it to len(Crafts)+1 and stamps any unstamped craft.
NextCraftID uint64
// NextNodeID is the monotonic source of stable ManeuverNode.IDs
// (v0.16 / ADR 0016). Stamped onto each node as it is planted; never
// decremented or reused, so a feature that follows one specific node
// (Auto-Warp's frozen target) can resolve it across the sortNodes
// reorder that runs on every plant. Persisted alongside NextCraftID;
// EnsureNodeIDs primes it past every live node ID and back-fills any
// node still carrying the zero (pre-field / legacy-save) value.
NextNodeID uint64
// AutoWarp is the engaged Auto-Warp driver, or nil when off (v0.16 /
// ADR 0016). While set, the tick max-seeds clampedWarp and ramps to a
// frozen target T = BurnStart − autoWarpLeadTime, then hands off to 1×.
// Transient — never persisted; a save/load mid-warp lands disengaged.
AutoWarp *AutoWarpTarget
// LastDockEvent records the most recent fusion for HUD flash
// + diagnostic. Cleared by app.go after the message is shown.
// v0.8.3+.
LastDockEvent *DockEvent
// LastLaunchReleaseEvent records the most recent ViewLaunch
// switch-end release so the App can surface an
// `"ORBIT READY — returning to <prev view>"` toast. Cleared by
// app.go after the message fires; same pattern as LastDockEvent.
// v0.11.0+ (the apoapsis-floor auto-release that also stamped
// this was retired by ADR 0021 D).
LastLaunchReleaseEvent *LaunchReleaseEvent
// Focus selects what the OrbitView canvas is centered on. Zero value
// (FocusSystem) matches v0.1.0 behavior.
Focus Focus
// Target is the unified pointer-at-the-thing-the-player-is-aiming-at.
// v0.9.0+: replaces the implicit body-cursor that pre-v0.9 PlanTransfer
// / PlanInclinationChange consumed via App.selectedBody, and absorbs
// the rendezvous target-craft idx that v0.9.3 will plumb. Zero value
// (TargetNone) means no target — every consumer falls back to its
// kind-less default (equatorial plane, Hohmann no-op).
Target Target
// interpret against (KSP-style nav-ball mode cycle). Zero value
// (NavOrbit) reproduces the pre-v0.9.3 behavior. Cycled via the
// `;` hot-key; auto-snaps to NavOrbit when a craft target is
// dropped. v0.9.3+.
NavMode NavMode
// ViewMode selects the canvas projection basis. v0.6.4+; v0.10.6+
// prepended ViewTilted as the new zero-value default. Set per-
// session via the `v` hot-key; not persisted to save (UI
// preference, not game state).
ViewMode ViewMode
// ViewTilt carries the polar tilt θ and yaw φ (degrees) that
// ViewTilted applies to the projection basis. v0.10.6+. Per-
// session UI state — not persisted. NewWorld seeds defaults via
// DefaultViewTilt(); struct-literal Worlds (test fixtures) get
// the {0, 0} zero value, which evaluates to an identity rotation
// and therefore behaves the same as ViewTop.
ViewTilt ViewTilt
// LaunchSessionActive is the v0.11.0+ ViewLaunch session sentinel.
// True between the per-tick route handler firing on an active-slot
// Landed-false→true transition and the manual `v` cycle out (or a
// switch onto a flying vessel — ADR 0021 D retired the apoapsis-
// floor auto-release). Distinguished from
// PrevViewMode because PrevViewMode's zero value (ViewTilted)
// collides with "no session" — the boolean is the unambiguous
// signal that session-scoped state (PrevViewMode, LaunchT0,
// LaunchMaxQ, LaunchTrail, LaunchZoom) is meaningful. Per-session
// UI state; not persisted (a reload re-routes naturally if the
// active craft is Landed).
LaunchSessionActive bool
// PrevViewMode is the ViewMode the player was in when the route
// handler routed into ViewLaunch — restored by the switch-end
// release (a manual `v` cycle advances instead of restoring).
// Meaningful only when LaunchSessionActive == true. Not persisted.
PrevViewMode ViewMode
// LaunchT0 is the sim-time stamp the route handler captured when
// the current session opened — the anchor for the HUD T+ readout.
// Meaningful only when LaunchSessionActive == true. Re-stamped on
// active-vessel-switch hand-off (treating the new active as a
// fresh launch from the switch moment). Not persisted.
LaunchT0 time.Time
// LaunchMaxQ is the peak dynamic pressure (Pa) observed across the
// current session — the HUD's max-Q readout. Cleared by the route
// handler at session open. Not persisted.
LaunchMaxQ float64
// LaunchTrail is the breadcrumb buffer of body-fixed (lat, lon,
// alt) samples the chase-cam scene re-projects each render so the
// trace visibly rotates with the body. FIFO cap 256, sampled at
// 1 s sim-time cadence. Cleared by the route handler at session
// open and by the switch-end release / hand-off. Not persisted.
LaunchTrail []TrailPoint
// LaunchZoom is the player's `+/-` zoom override for the chase-cam
// scene. 0 means auto-altitude-driven scale; non-zero pins the
// scale until session end (manual `v` cycle, switch-end, hand-off,
// or next route). Multiplicative ×0.8 per `+`, ×1.25 per `-`. Not
// persisted.
LaunchZoom float64
// InstantSAS opts back into the legacy instantaneous-attitude
// path. v0.10.0+ makes rate-limited slew the DEFAULT (zero value
// = false = slew on); toggling this true restores the pre-v0.10
// "magic SAS" snap (the byte-identical regression baseline). Like
// ViewMode it is a per-session UI preference — NOT persisted; a
// reload returns to the slew default. Surfaced as a new hot-key +
// navball [MODE] MANUAL/AUTO tag (binding is the app/render
// slice's call).
InstantSAS bool
// LastTransfer (v0.12.x) records the dual-strategy Δv comparison from
// the most recent intra-primary [H] auto-plant (combined fused-Lambert
// vs split raise+plane-change), for the HUD flash. Not persisted —
// a transient UI artifact of the last plant. See PlanTransfer / ADR 0005.
LastTransfer TransferComparison
// Missions are pass/fail objectives evaluated against World state
// each Tick. Seeded from the embedded starter catalog at NewWorld
// time; Status fields progress as the player flies. v0.6.5+.
Missions []missions.Mission
// GroundStations is the CommNet ground-station catalog (v0.23 / ADR
// 0027): the home-body DSN ring plus any user overlay, loaded at
// NewWorld. Each station co-rotates with its body; its world position
// is computed on demand (the connectivity graph slice). Data-driven —
// extra stations or stations on other bodies are just catalog entries.
// Not persisted (rebuilt from the catalog on load).
GroundStations []GroundStationPreset
// Ghosts are other players' craft evaluated at THIS world's
// sim-time (v0.27 S5, ADR 0034 — Kepler ghosts). Written each tick
// by the multiplayer serve layer from the session store; empty in
// single-player. Render-only and transient: never persisted, never
// physically interactive, already gated to this world's active
// system by the writer.
Ghosts []Ghost
// CoWarp is the transient proximity co-warp slate (v0.28 S1, ADR
// 0034 §5): written each tick by the serve layer from ComputeCoWarp,
// read by clampedWarp for the min-over-coupled-players clamp. Same
// contract as Ghosts — serve-written, transient, never persisted,
// zero-value (uncoupled) in single-player.
CoWarp CoWarpState
// RendezvousArm is the viewer's outgoing Rendezvous Warp intent (v0.29
// S1, ADR 0034 v0.29 addendum; a standing mutual intent since #252):
// the partner Engaged toward and the CURRENT waypoint's encounter
// sim-time — it survives reaching τ, which only advances the waypoint.
// Set by EngageRendezvousWarp, read by ComputeCoWarp for the
// mutual-arm couple trigger and by the serve layer to relay the
// intent. Transient like CoWarp/AutoWarp — never persisted, cleared on
// cancel, on the proximity handoff at couple range, and on partner
// disconnect; nil in single-player.
RendezvousArm *RendezvousArm
// RendezvousInvite is the incoming half of the mutual arm (v0.29 S2):
// a peer armed toward the viewer who has not Engaged back yet.
// Refreshed each tick by DriveRendezvousWarp from the co-warp peer
// set; the orbit HUD renders the persistent join prompt from it. Nil
// while the viewer holds an outgoing arm (pairwise MVP — respond or
// cancel first) and once the committed τ has passed. An invite across
// a subspace gap survives with Blocked set (#250) — attributable but
// not joinable. Transient, serve-written like CoWarp.
RendezvousInvite *RendezvousInvite
// RendezvousHold freezes the viewer's effective warp while the shared
// coast runs and the armed partner is paused or behind-diverged
// (v0.29 review): the coast leader waits instead of sailing to τ
// alone and blowing the subspace tolerance. Set each tick by
// DriveRendezvousWarp, read by clampedWarp — a member of the
// Effective-≤-Selected clamp family. Transient, serve-written.
RendezvousHold bool
// RendezvousPartnerAway mirrors the armed partner's live Away state
// (#253, ADR 0036): their session still flies — a Commitment Reprieve
// holds it open — but nobody is at the controls. Set each tick by
// DriveRendezvousWarp from the co-warp peer slate, rendered by the
// RENDEZVOUS chip as a standing line; Away is a state that lasts
// hours by design, so the flight view must not depend on the 6 s
// went-quiet SessionEvent to know it. Transient, serve-written like
// RendezvousHold.
RendezvousPartnerAway bool
// RendezvousWait classifies why an armed Rendezvous Warp has not
// started coasting (#250): genuinely waiting on the partner vs a
// subspace gap someone warped open (with the signed Δt saying who is
// ahead). Set each tick by DriveRendezvousWarp, read by the armed
// RENDEZVOUS chip so it stops blaming the partner for a self-made
// gap. Transient, serve-written like RendezvousHold.
RendezvousWait RendezvousWait
// RendezvousRate is the terminal phase's standing rate slate (ADR 0037
// §2): the viewer's seat, the ceiling the pair's rate is held to, and
// who is holding it. Set each tick by DriveRendezvousWarp, read by
// clampedWarp (another member of the Effective-≤-Selected family) and
// by the RENDEZVOUS chip, which renders the holder so a locked warp key
// always explains itself. Transient, serve-written like RendezvousHold;
// the zero value is every solo tick.
RendezvousRate RendezvousRateState
// RendezvousDegraded / RendezvousApproachM are the hold-τ degrade slate
// (v0.29 S1): while the shared coast runs, DriveRendezvousWarp
// recomputes the approach at the committed τ each tick and sets
// Degraded when the encounter has worsened past a recent baseline by
// more than an encounter-scaled bar (#251 — see degradeSlipFrac /
// degradeRebaseAfter in cowarp.go). ApproachM is the live approach
// for the S2 warning chip. Transient, serve-written like CoWarp.
RendezvousDegraded bool
RendezvousApproachM float64
// Session and SessionEvents are the multiplayer roster slate and
// recent join/leave/sync moments (v0.27 S6) — same contract as
// Ghosts: serve-layer written, screen read, transient, nil/empty
// in single-player.
Session *SessionInfo
SessionEvents []SessionEvent
// ChatLines is the viewer's slice of the transient chat ring
// (ADR 0035) — same contract as SessionEvents, but its own ring on
// the serve side so chat volume can never evict session moments.
ChatLines []ChatLine
// DockGuest is set when one of this player's craft rides in another
// player's live stack (Docked-as-Guest, v0.28 S5, ADR 0034 §6). The
// serve layer writes it each tick from the dock ledger; the guest's
// whole subspace is then warp-coupled (min-wins) to the stack via
// CoWarpState.WithDockCoupling folded into CoWarp, so the guest can
// always slam 1× and burn but can't out-warp the owner. nil when not
// a guest. Transient, never persisted (the persisted cross-ref lives
// in the session directory). Also drives the tui: Undock while a
// DockGuest is set signals the owner to split, rather than a local
// Undock.
DockGuest *DockGuestLink
// LastSyncArrival is set when a Sync warp reaches its target time
// (v0.27 S7) and cleared by the serve wrapper after firing the
// arrival chips. Transient, like LastDockEvent.
LastSyncArrival *SyncArrival
// LastRendezvousArrival is set when a Rendezvous Warp coast reaches a
// waypoint inside proximity couple range — the handoff that ends the
// standing intent (v0.29 S1, #252) — and cleared by the serve wrapper
// after firing the arrival chip. Transient, like LastSyncArrival.
LastRendezvousArrival *RendezvousArrival
// LastRendezvousWaypoint is set when the coast reaches a waypoint
// OUTSIDE couple range and advances to a newly derived encounter
// (#252) — the intent continues. Consumed by the serve wrapper for
// the waypoint chip. Transient, like LastRendezvousArrival.
LastRendezvousWaypoint *RendezvousWaypoint
// CommGraph is the cached per-tick CommNet connectivity result (v0.23 /
// ADR 0027): which unmanned probes currently reach a ground station.
// Rebuilt each Tick by RecomputeCommGraph after physics; read by
// CanCommandCraft + coverage objectives + the comms HUD. Transient.
CommGraph *CommGraph
// contains filtered or unexported fields
}
World holds the simulation state: loaded systems, active-system index, the sim-clock, and — post-C15 — the spacecraft.
func NewWorld ¶
NewWorld loads the embedded systems, seeds clock at J2000 + 50 ms base step, and spawns a spacecraft in LEO around Sol's Earth.
func (*World) ActiveCommPath ¶ added in v0.22.0
ActiveCommPath returns the active craft's relay chain as ordered world-frame points (probe, relays…, station), the hop count (number of links = len(points)-1), and whether it is connected. Recomputes the graph lazily if the cache is nil. connected is false for a crewed/non-probe active craft (which has no BFS path) or a disconnected probe — the comms HUD uses that to choose between DIRECT/CONNECTED and NO SIGNAL, and to decide whether to draw the chain. (C2-7)
func (*World) ActiveCraft ¶ added in v0.8.1
func (w *World) ActiveCraft() *spacecraft.Spacecraft
ActiveCraft returns the currently-controlled craft, or nil if no craft is loaded. v0.8.1+. All historical call sites that read `w.ActiveCraft()` now go through this accessor.
func (*World) ActiveMission ¶ added in v0.6.5
ActiveMission returns the first in-progress mission whose requirements are met — the rung the player is actually working on — or nil if none qualifies (all passed/failed/locked, or none loaded). v0.6.5+. The requires gate (ADR 0025 §8, v0.21 Slice 6) matters because the evaluator now skips locked missions, so a locked mission stays InProgress forever: without this gate the in-flight chip (the only caller) could surface a locked mission with frozen progress while the ladder screen — which classifies the same mission as locked — shows a different active card. Both surfaces now share one definition of "active". Used by the HUD to surface a single-line status.
func (*World) AdjustStartForLunarTransferWindow ¶ added in v0.13.0
AdjustStartForLunarTransferWindow shifts the world clock so the next ideal Moon-transfer departure (the split-strategy line-of-nodes crossing the planner would plant on `H`) sits ~lead away, instead of the ~10 days out that the J2000 epoch yields. It mutates only the clock — the craft and the calculator are untouched — so the Moon's phase (a pure function of SimTime since the J2000-anchored ephemeris fix) moves with the chosen start.
Returns false and leaves the clock at its original value if there's no Moon, no craft in orbit around it, or no start within tolerance can be found. A false return is harmless: the game simply opens at J2000.
func (*World) AdjustThrottle ¶ added in v0.7.3
AdjustThrottle steps the active craft's Throttle by delta, clamped to [0, 1].
func (*World) AdoptCraft ¶ added in v0.28.0
func (w *World) AdoptCraft(c *spacecraft.Spacecraft, makeActive bool) int
AdoptCraft appends a craft carrying a stable ID into the slate on the receiving side of a cross-player handoff (v0.28 S5) — the docker adopting a handed-over guest craft, the guest receiving its component back on undock, or a Transfer-Control recipient adopting the whole migrating composite. Craft-ID spaces are PER-WORLD and independent (both start low), so an incoming ID from the origin World can collide with a native craft already in this slate; when it does — or when the craft carries no ID at all — we stamp a FRESH id from NextCraftID (restamped in place, so a caller reading c.ID after the call sees the new id). A non-zero, unused incoming ID is PRESERVED (the guest-gets-its- component-back case, which must keep guestCraftID; NextCraftID only moves forward, so a preserved id never collides with a future local spawn). Advances NextCraftID past a preserved ID and optionally makes it active. Returns the new slate index.
func (*World) ApplyStartScenario ¶ added in v0.17.0
func (w *World) ApplyStartScenario(s StartScenario) error
ApplyStartScenario reshapes the world's starting craft per s, replacing the default LEO seed. It resolves the system + body, clears the default slate, spawns a single craft from the scenario via SpawnCraft (so launchpad co-rotation and ADR 0015 system binding come for free), and leaves that craft active with the camera following it. Returns a descriptive error (listing valid values) on an unknown system / body / loadout, leaving the world untouched.
func (*World) ArrivalCapturePreview ¶ added in v0.8.2
func (w *World) ArrivalCapturePreview() (CapturePreview, bool)
ArrivalCapturePreview returns a CapturePreview for the last node in the active craft's plan that lands in a different primary's frame, or ok=false when no such node is queued. v0.8.2.x.
func (*World) AutoWarpEligible ¶ added in v0.16.0
AutoWarpEligible reports whether engaging right now would find a burn to chase — drives the dimmed/active state of the title-bar button.
func (*World) AutoWarpEngaged ¶ added in v0.16.0
AutoWarpEngaged is the exported form for the tui (HUD chip + button state).
func (*World) AutoWarpSecondsToTarget ¶ added in v0.16.0
AutoWarpSecondsToTarget returns the sim-seconds until the engaged driver's release point T, and ok=false when not engaged — feeds the `AUTO → Nx ⏱ Ms` HUD chip.
func (*World) BodyPosition ¶
func (w *World) BodyPosition(b bodies.CelestialBody) orbital.Vec3
BodyPosition returns the inertial position (m) of a body in the current system at the current sim time. Convenience wrapper over BodyPositionAt at w.Clock.SimTime.
func (*World) BodyPositionAt ¶ added in v0.8.2
BodyPositionAt returns the inertial position (m) of a body at an arbitrary sim time. Primary (index 0) is anchored at origin; bodies with ParentID resolve recursively as parent + position- relative-to-parent. v0.8.2.x: the time-aware variant lets the chained-prediction path snapshot bodies at each node's actual trigger time rather than at SimTime, which fixes inclination previews on multi-day transfers (Luna moves ~30° in 3 days, so using SimTime body positions misplaces the arrival rebase by the same amount).
func (*World) BodySOIRadius ¶ added in v0.18.3
func (w *World) BodySOIRadius(b bodies.CelestialBody) float64
BodySOIRadius returns b's parent-relative sphere-of-influence radius in the viewed system — a moon's SOI against its planet, a planet's against the system root — falling back to the root when ParentID doesn't resolve, exactly as physics.FindPrimary sizes its spheres. This is the one radius the SOI Ring draws at and the framing fallbacks floor to (ADR 0021 C, issue #143): computing it against the system root instead hands a moon the absurd Sun-relative value the SOIRadius / FindPrimary comments warn about. 0 for the system primary itself (no orbital data).
func (*World) CanCommandCraft ¶ added in v0.22.0
func (w *World) CanCommandCraft(c *spacecraft.Spacecraft) bool
CanCommandCraft reports whether the player may issue NEW commands to a craft (ADR 0027 §4 — command, not flight). A crewed vessel is never gated; an unmanned probe needs a network connection; passive debris (no command source) is never commandable. The onboard flight plan still executes regardless — only committing new commands is gated by the caller.
func (*World) ClearNodes ¶ added in v0.2.0
func (w *World) ClearNodes()
ClearNodes wipes every pending node from the active craft. v0.8.1+: per-active-craft (was global pre-v0.8.1).
func (*World) ClearTarget ¶ added in v0.9.0
func (w *World) ClearTarget()
ClearTarget drops any target. After ClearTarget, Target.Kind == TargetNone. Also reconciles NavMode (snap NavTarget → NavOrbit) so the HUD doesn't claim a mode it can no longer resolve. v0.9.3+.
func (*World) CoWarpCoupled ¶ added in v0.28.0
CoWarpCoupled reports whether the viewer is warp-coupled to any player this tick — read by the split guard (EngageSyncWarp) and available to the HUD. v0.28 S1.
func (*World) CommBandCoverage ¶ added in v0.32.0
CommBandCoverage samples connectivity for a hypothetical probe with the given antenna rated range (≤0 → the direct-basic backfill every non-debris vessel carries) in a circular EQUATORIAL orbit of bodyID at altM, over orbit phase × body rotation phase, and returns the connected fraction. The antenna matters (v0.32 review finding): a Relay-Tug at the Moon links to Earth's ring while a direct-basic probe there is genuinely out of reach — warning the relay spawn "out of network reach" would steer the player off the exact constellation the band exists to motivate. ok=false when the body is not in the viewed system. Equatorial in the body-equatorial frame (frame.go is the boundary) — the amendment's scratch harness sampled ecliptic- frame orbits and its inclination figures were confounded with axial tilt; the form spawns exactly one plane (posOrbit has no inclination field), and this samples that plane. Live craft are deliberately excluded: the label describes the preset band of the station model, deterministic per (body, altitude, antenna), not the player's current relay constellation.
func (*World) CommBlockedFlash ¶ added in v0.22.0
CommBlockedFlash returns ("NO SIGNAL", true) while a just-blocked command is within its flash window, else ("", false). The HUD reads this each frame (the comms chip / status flash, C2-7).
func (*World) ComputeCoWarp ¶ added in v0.28.0
func (w *World) ComputeCoWarp(peers []CoWarpPeer, prev map[string]bool) CoWarpResult
ComputeCoWarp evaluates proximity co-warp against the viewer's active craft (the anchor) for this tick. `prev` is the per-owner coupled map returned last tick — it supplies the hysteresis memory so a coupled pair uses the wider decouple gate. The returned CoupledOwners becomes next tick's `prev`. No World mutation — the caller assigns State to w.CoWarp after this returns — so it's testable with hand-built peers; it does, however, READ w.CoWarp (the state the caller committed from the PREVIOUS call, still unoverwritten at this point) for the omission sweep below, the one input that doesn't round-trip through `prev`.
Anchor gating (ADR 0015 / 0025 precedent): only the viewer's active craft anchors co-warp in the MVP — a passive craft of the viewer near a partner won't couple. A landed or missing anchor couples to nobody.
func (*World) CounterfactualSOIPass ¶ added in v0.18.0
CounterfactualSOIPass is the dual-arc "no-burn" pass (ADR 0019 D): the live trajectory's upcoming pass, but capped at the first planted node so the counterfactual is never predicted — or drawn — past the burn the craft will actually make. With no node planted it is identical to LiveSOIPass. ok=false when the first node is already due (cap ≤ 0).
func (*World) CraftByID ¶ added in v0.14.5
func (w *World) CraftByID(id uint64) (*spacecraft.Spacecraft, int, bool)
CraftByID is the exported resolver for readers outside the sim package (the tui screens) that hold a stable craft ID — e.g. a node's TargetCraftID — and need the live craft + its current slate index. ok=false for id==0 or a craft no longer in the slate (ADR 0012).
func (*World) CraftInertial ¶
CraftInertial returns the spacecraft's inertial position (Sun-centered) for rendering on the heliocentric canvas. Adds craft's primary-centric position to the primary's inertial position.
func (*World) CraftInertialVelocity ¶ added in v0.9.0
func (w *World) CraftInertialVelocity(c *spacecraft.Spacecraft) orbital.Vec3
CraftInertialVelocity returns a craft's velocity in the system- inertial (heliocentric) frame. Mirrors CraftInertial for position. Useful to consumers outside the sim package (HUD readouts, target resolution) that need a craft's inertial state without re-doing the primary-velocity addition. v0.9.0+.
func (*World) CraftTrail ¶ added in v0.5.2
CraftTrail returns the trail samples in oldest-to-newest order, each translated into current-tick inertial coordinates via BodyPosition(sample.primary). The returned slice is a fresh copy — callers can iterate / reverse safely. Empty when the craft has just spawned and hasn't accumulated trailIntervalSec of sim time yet.
Note: the inertial positions returned here move with the body each frame — a stationary LEO craft over 100 ticks produces samples whose raw stored .relR is identical, but whose translated inertial drifts with Earth. The trail effectively floats with the primary, which is what the player sees (Earth is fixed at canvas center under FocusBody, and the trail loops around it).
func (*World) CraftVisibleHere ¶
CraftVisibleHere reports whether the Active Vessel should be drawn in the currently-viewed system. v0.16 / ADR 0015: true iff the Active Vessel is bound to the viewed System. The camera follows the Active Vessel (SetActiveCraftIdx snaps w.SystemIdx), so this is normally true; it goes false only while the player is browsing another System via CycleSystem.
func (*World) CycleActiveCraft ¶ added in v0.8.1
CycleActiveCraft advances ActiveCraftIdx by delta (typically +1 or -1), wrapping at the slate's boundaries. No-op when fewer than two craft are loaded. v0.8.1+.
func (*World) CycleEngineMode ¶ added in v0.8.0
func (w *World) CycleEngineMode()
CycleEngineMode flips between EngineMain and EngineRCS, the v0.8.0 `r` keystroke target. Stops any in-flight manual main burn before switching to RCS so the engine doesn't keep firing through the mode change — the player would expect `r` to mean "switch tools," not "keep burning while I switch tools."
func (*World) CycleFocus ¶ added in v0.2.0
CycleFocus advances the focus to the next target (or previous if forward=false). Order: System → Body(0) → Body(1) → … → Body(n-1) → Craft (only if CraftVisibleHere) → System.
func (*World) CycleNavMode ¶ added in v0.9.3
CycleNavMode advances World.NavMode through Orbit → Surface → Target → Orbit. NavTarget is skipped when no craft target is bound so the player never lands on a mode that silently degrades back to orbit. Returns the new mode for the caller's HUD flash.
func (*World) CycleRCSPulseScale ¶ added in v0.24.5
func (w *World) CycleRCSPulseScale()
CycleRCSPulseScale steps the active craft's RCS pulse magnitude to the next fine level, wrapping after the finest: 0.1 → 0.01 → 0.001 → 0.1 m/s. The dedicated `p` fine-trim key; mirrors CycleEngineMode. No-op without an active craft. Only RCS pulses read the level, so cycling in main-engine mode is harmless — the new step takes effect once `r` switches to RCS. v0.24.5+.
func (*World) CycleSystem ¶
func (w *World) CycleSystem()
CycleSystem advances to the next system (wraps). Recreates the calculator. v0.16 / ADR 0015: this is a browse-only "telescope" view toggle — the Active Vessel does NOT follow. Cycling to a System that isn't the Active Vessel's hides the flight HUD (CraftVisibleHere is false there) and shows that System's map; switching active (SetActiveCraftIdx) or cycling back snaps the view home. This browse-then-spawn flow is also the only way to get a Vessel into another System: a spawned Vessel binds to the *viewed* System at spawn time. Resets focus to system-wide because body indices don't carry across systems.
func (*World) CycleTarget ¶ added in v0.9.0
CycleTarget advances Target through non-active sibling crafts → system bodies (non-root) → None → repeat. Forward=false steps backwards through the same cycle. No-op when no targetable entry exists.
Cycle order: every non-active craft in the slate first (the small set the player most often wants to target after spawning a sister craft), then bodies in the current system (idx 1 .. n-1, skipping the system primary which has no orbital radius), then TargetNone, then repeat. Sibling-frame restriction is intentionally not enforced on the craft branch so the player can pre-select a target before transferring into its frame.
func (*World) CycleViewMode ¶ added in v0.6.4
func (w *World) CycleViewMode()
CycleViewMode advances ViewMode to the next mode in cycle order. Wraps around — modes are a small finite set. v0.11.0+: leaving ViewLaunch via manual cycle clears LaunchSessionActive — and with ADR 0021 D this `v` cycle is THE way out of the chase cam (the apoapsis-floor auto-release is retired). ViewMode still advances by one (cycle semantics are *advance*, not *restore* PrevViewMode). A ViewMode change is a Framing Event (ADR 0021 A): the orbit screen refits the canvas once in response, then leaves the camera alone.
func (*World) DeleteNode ¶ added in v0.8.6
DeleteNode removes the node at idx from the active craft's plan. Out-of-range idx is a no-op (callers may pass -1 to indicate "no edit target"). v0.8.6+ — paired with the maneuver form's per-node delete action that replaces the v0.8.5-and-earlier "wipe everything via N" keybinding.
func (*World) DensifyForeignArcs ¶ added in v0.19.0
func (w *World) DensifyForeignArcs(segs []SOISegment) []SOISegment
DensifyForeignArcs replaces each foreign-SOI segment that carries an entry conic (HasEntryConic — recorded by the predictor at an SOI-crossing rebase) with its analytic redraw (ADR 0023 D), so the planted-node legs draw a sharp perilune as a smooth curve instead of the equal-time integrated sampling's facets (~0.45× perilune-radius chords at a tight perilune). Home / parent segments and any without a recorded entry conic pass through unchanged. The periapsis clock comes from the entry point's analytic time-from-periapsis, the same timing analyticArcSegment uses per sample. Pure conic trig, no re-integration; the leg-draw builder wraps PredictedSegmentsFrom with it.
func (*World) Deploy ¶ added in v0.23.0
Deploy releases the **topmost** nose payload of the composite at idx as its own craft while keeping the carrier active — the drop-and-continue verb for constellations and tugs (ADR 0028 decisions 3/4/7). Each press pops one payload: the last DockedComponent (the top of the stack) is peeled off, leaving the carrier flying with one fewer payload; once only the carrier core remains it reverts to a plain craft. Emits the `deploy` semantic action (ADR 0025 vocab) for the mission model.
Contrast with Undock, which explodes the whole composite at once and switches the active craft to a released component. Deploy reuses Undock's per-component synthesis (separation push, command-source backfill, stable ID) via restoreComponentCraft but releases just the top and never changes the active craft. No-op (returns false) when the craft is not a loaded composite (fewer than two components). v0.23 / ADR 0028 C3-2.
func (*World) DisengageAutoWarp ¶ added in v0.16.0
func (w *World) DisengageAutoWarp()
DisengageAutoWarp releases the driver without touching Selected Warp, so the player falls back to exactly the warp they had. This is the manual-cancel / node-invalidated path; the reached-target path in resolveAutoWarp additionally forces WarpIdx to 1×.
A rendezvous coast is arm + driver as one unit (v0.29 review): every disengage path — [G] toggle, [/] — must clear the arm too, or DriveRendezvousWarp restarts the coast (and force-unpauses) on the next serve tick, making the cancel a silent no-op. The manual warp keys no longer reach here during an engaged coast (#249): their intent is "adjust the rate", not "cancel", so the tui refuses them with a toast instead of calling this.
func (*World) DisengageRendezvousWarp ¶ added in v0.29.0
func (w *World) DisengageRendezvousWarp()
DisengageRendezvousWarp cancels the viewer's Rendezvous Warp: clear the arm and, if the shared coast had started, release the Auto-Warp (Selected Warp untouched). Either player's cancel releases both — the retraction travels the wire and the partner's DriveRendezvousWarp sees the arm vanish and cancels in turn.
func (*World) DockCrafts ¶ added in v0.8.3
DockCrafts fuses two craft (by slate index) into a single composite. Mass-weighted centroid for position; momentum- conserving combination for velocity; summed pools for fuel, monoprop, and capacities; concatenated roles. The active partner's name, glyph, color, nodes, and engine state survive — its loadout becomes the composite's identity. The other partner is removed from the slate. v0.8.3+.
If neither partner has a main engine, the composite ends up RCS-only (Thrust=0). If only one has a main, the composite inherits that engine.
ActiveCraftIdx is adjusted so the player keeps flying the composite (the active partner's slate position).
func (*World) DockGuestCraft ¶ added in v0.28.0
func (w *World) DockGuestCraft(dockerIdx int, guest *spacecraft.Spacecraft, guestOwner string) (*spacecraft.Spacecraft, int, bool)
DockGuestCraft fuses guest into the docker's craft at dockerIdx, producing one composite OWNED by this World (the docker's). The composite keeps the docker's identity; guest rides as one or more DockedComponents tagged with guestOwner (the guest player's fingerprint) so UndockGuest can peel exactly the guest's sub-stack back out. guest is appended to the slate and consumed by the fusion — the caller (serve, after the guest World handed the payload over) passes a craft that already carries its pre-dock stable ID. Returns the composite, its slate index, and ok=false for a bad index / nil guest / failed fuse.
Ownership: the docker is always the fusion lead, regardless of which slot is active, so the stack's identity + this World's simulation authority are the docker's per ADR 0034 §6. The guest's contributed components are the trailing entries of the flattened DockedComponents list (fuseComposite appends the drop's components last); tagging those with guestOwner is the whole cross-player marking.
func (*World) DockGuestStackGhost ¶ added in v0.35.0
func (w *World) DockGuestStackGhost() (Ghost, *bodies.CelestialBody, bool)
DockGuestStackGhost resolves the stack this player rides in to its live ghost + SOI primary (ADR 0038 §2 "badged panels"): the VESSEL/ORBIT chips read this to show the stack's real flight data — badged with the owner's handle — instead of the bare "why is this empty" placeholder. ok is false with no DockGuest, no ghost report yet (owner hasn't reported since the fuse), or the ghost's primary not resolving in the viewer's current system (owner in a different system/body) — every case degrades to the caller's existing empty-slate fallback rather than a dangling reference.
func (*World) DockOwnerOnline ¶ added in v0.35.0
DockOwnerOnline reports whether the stack owner named by DockGuest has a live Session right now (ADR 0038 amendment 3 / ADR 0040 §4's empty-seat gate). This is the SAME presence gate the reclaim flow already uses — Server.presence.isOnline — reused here via the roster the reporting layer already fills in each tick (SessionInfo.Players[].Online) rather than plumbing a second copy through the docking ledger. Distinct from DockGuest.OwnerAway, which is true for an idle-but-still-connected owner (ADR 0036): an Away owner still holds their seat, so [u] stays theirs to grant; only a Session that is not live at all is an empty seat. False (renders as empty-seat) with no DockGuest, or no matching roster row — conservative by design, since an unconfirmed owner should not offer the "ask them" flow.
func (*World) DriveRendezvousWarp ¶ added in v0.29.0
func (w *World) DriveRendezvousWarp(peers []CoWarpPeer)
DriveRendezvousWarp starts, holds, advances, or cancels the shared coast of the standing rendezvous intent from this tick's mutual-arm state (v0.29 S1, reshaped for #252). Called each tick after the co-warp peers are built. The coast starts only once BOTH players are armed toward each other in the same Subspace (no solo drift); a genuine retract or disconnect mid-coast cancels — either side's cancel releases both. Reaching the committed τ is resolved HERE, not in resolveAutoWarp, because deciding between the proximity handoff and a waypoint advance needs this tick's peer set (craft ranges + relayed τs), which the sim tick path doesn't carry. An armless world with the coast still flagged just releases it here defensively.
func (*World) EffectiveWarp ¶
EffectiveWarp exposes the clamped warp for HUD display. Returns the same as Clock.Warp() when the user isn't hitting the step-size guard.
func (*World) EndFlightActive ¶ added in v0.11.4
EndFlightActive removes the active vessel from the slate IFF it is Crashed. Returns true when the removal happened. A no-op (returns false) when there is no active vessel or the active vessel is not Crashed — the screen-side confirm prompt should gate on `c.Crashed`, but defence-in-depth here avoids accidental removal via direct API calls.
Active reassignment: the slate is left in its natural order (no resort); ActiveCraftIdx snaps to the same index when there's a successor at that slot, or to (len-1) when the removed entry was the tail. Empty slate → ActiveCraftIdx = -1.
func (*World) EndRendezvousOnDock ¶ added in v0.33.0
EndRendezvousOnDock releases a standing agreement with partner because the pair have docked — one of ADR 0037 §1's exactly two end conditions (the other being an explicit cancel). Reports whether it ended one, so the serve layer can suppress the cancel chip: docking is the rendezvous succeeding, and the dock's own moment already says so.
func (*World) EngageAutoWarp ¶ added in v0.16.0
EngageAutoWarp aims the driver at the globally-soonest eligible burn across all vessels and returns true on success. Eligible ⇔ BurnStart is more than autoWarpLeadTime in the future; otherwise the press is a no-op returning false (the button is dimmed). Engaging while paused auto-unpauses so time actually advances.
func (*World) EngageRendezvousWarp ¶ added in v0.29.0
func (w *World) EngageRendezvousWarp(partner, handle string, tau time.Time, committedCA float64) bool
EngageRendezvousWarp records the viewer's Rendezvous Warp intent toward partner, committed to the encounter sim-time tau — the initiator's authoritative TCA, which becomes the standing intent's FIRST waypoint (#252) (v0.29 S1, ADR 0034 v0.29 addendum). handle is the partner's display name, captured here so chips and the HUD never have to resolve a fingerprint through a possibly-stale roster. It does NOT start the shared coast: DriveRendezvousWarp starts it only once the partner has Engaged back, so the first to Engage never warps solo. Forward-only (tau at/behind SimTime is refused — the laggard Syncs forward). Replaces any prior arm.
func (*World) EngageRendezvousWarpAs ¶ added in v0.33.0
func (w *World) EngageRendezvousWarpAs(partner, handle string, tau time.Time, committedCA float64, initiator bool) bool
EngageRendezvousWarpAs is EngageRendezvousWarp with the seat named (ADR 0037 §2). The Session screen's row action arms as the INITIATOR — pilot-in-command of the pair's time once the terminal phase begins — and the main-screen [y] join arms as the copilot, which is what the plain EngageRendezvousWarp wrapper above does. Roles are fixed here, at invite time, and relayed, so neither side can drift into disagreeing about who flies the clock.
func (*World) EngageSyncWarp ¶ added in v0.27.0
EngageSyncWarp aims Auto-Warp at a fixed sim-time — Sync to another player (v0.27 S7, ADR 0034). Forward only: a target at or behind SimTime returns false (the laggard always comes forward; rewinding would fork recorded history). handle labels the arrival chip. Engaging replaces any node-chase in progress and auto-unpauses.
func (*World) EnsureCraftIDs ¶ added in v0.14.5
func (w *World) EnsureCraftIDs()
EnsureCraftIDs advances NextCraftID past any ID already in the slate and stamps every craft still missing one. Idempotent; called from NewWorld and the save loader so every live craft carries a unique stable ID and the counter never hands out a colliding value.
func (*World) EnsureNodeIDs ¶ added in v0.16.0
func (w *World) EnsureNodeIDs()
EnsureNodeIDs advances NextNodeID past any node ID already in the slate and stamps every planted node still missing one. Idempotent; called from NewWorld and the save loader so every node carries a unique stable ID and the counter never hands out a colliding value (the node-level analogue of EnsureCraftIDs).
func (*World) EntryPosition ¶ added in v0.18.3
EntryPosition returns the SOI Entry marker's canvas position under the Local-to-Body Arc rule (ADR 0021 C): the pass Body's CURRENT position plus the body-relative ring-crossing offset — the same anchoring SegmentDrawPoints gives the arc and PerilunePosition gives the Perilune, so the glyph rides the drawn ring crossing. Callers gate on HasEntry.
func (*World) ExitPosition ¶ added in v0.18.3
ExitPosition is EntryPosition for the SOI Exit crossing; gate on HasExit.
func (*World) FireRCSPulse ¶ added in v0.8.0
func (w *World) FireRCSPulse(mode spacecraft.BurnMode) bool
FireRCSPulse delivers one RCSDvQuantum pulse in the given mode (the six attitude directions). No-op if the engine isn't in RCS mode, the craft is missing, monoprop is empty, or a planted finite burn is in flight (the planted burn owns the engine — RCS pulses while a main burn fires would muddy the integrator's force model).
Does NOT touch AttitudeMode: RCS is a 6-axis translation tool, so pulses apply Δv along the requested orbital-frame direction without re-pointing the nose. SAS hold is controlled separately via the main-engine attitude keys / SetAttitudeMode.
v0.8.0+.
func (*World) FocusIsEncounterFramed ¶ added in v0.20.0
FocusIsEncounterFramed reports whether the current body focus is being framed for an active SOI pass (ADR 0021 F) — i.e. FocusZoomRadius returned the ~1.3× parent-SOI encounter fit rather than the default surface-viewing fit. The orbit screen uses this to leave the wide encounter framing alone instead of zooming in to show the body's surface texture.
func (*World) FocusName ¶ added in v0.2.0
FocusName returns a short human label for the current focus.
func (*World) FocusPosition ¶ added in v0.2.0
FocusPosition returns the inertial (system primary-centric) position of the current focus target. Origin for FocusSystem.
func (*World) FocusZoomRadius ¶ added in v0.2.0
FocusZoomRadius suggests a world-space radius for auto-fit. Canvas.FitTo uses ~90% of the smaller pixel axis, so a returned radius R yields a frame that comfortably shows a circle of radius R around the focus.
This is the single Framing-Event fit resolution (ADR 0021 A): the orbit screen calls it exactly once per Framing Event (Focus change, ViewMode change, System switch), never per frame. The fit *value* may read sim state (the encounter-aware branch below) — the fit *timing* never does.
func (*World) FocusedBody ¶ added in v0.20.0
func (w *World) FocusedBody() (bodies.CelestialBody, bool)
FocusedBody returns the body the camera is focused on, when Focus is a FocusBody with a valid index. ok is false for system/craft focus or an out-of-range index.
func (*World) FollowDockGuestStack ¶ added in v0.35.0
func (w *World) FollowDockGuestStack()
FollowDockGuestStack keeps the rider's camera on the stack they are riding (ADR 0038 §2 — the follow-stack camera). The guest's own Crafts slate goes empty at the fuse (#301), so FocusCraft has nothing left to track; rather than invent a new Focus kind, this reuses Spectate's FocusGhost (v0.28 S6) — "watch a remote craft" is exactly the rider's situation. Called unconditionally every tick from the serve layer's refreshSession, so it must not fight the player: the rider camera is a convenience, not a lock (#331). The follow is a Framing Event only when the *tracked ref* itself changes — entering the ride, or the owner switching to a different active craft — never merely because w.Focus currently disagrees with it. That disagreement is exactly what a player-initiated Focus write (f/g's CycleFocus Spectate-exit, Spectate on a third player, focusNewCraft after a mid-ride spawn) looks like, and it must stick until the ride itself moves.
dockGuestFollowedRef (on World) records the last ref this function itself wrote — not the last Focus value, which the player is free to overwrite. Comparing the desired ref against that latch, rather than against the live w.Focus, is what tells "the ref changed, re-fit" apart from "the player changed Focus away from a ref that hasn't moved, leave it be". Once released this way the follow re-engages automatically the moment the ref does change (owner switches craft), per the ADR 0038 §2 ruling: convenience, not a permanent lock.
func (*World) GuestReleaseRefusal ¶ added in v0.33.0
GuestReleaseRefusal says, in the player's words, why the owner of the stack at idx cannot release its guest's component — or "" when the release will go through. It is the owner-seat sibling of UndockRefusal: UndockRefusal guards the LOCAL split (which must never touch a cross-player stack, since splitting locally would clone the guest's craft into this World), while this guards the release that travels through the dock ledger.
ADR 0040 §3 makes the docker able to release regardless of guest presence — "you can always get your own ship back to yourself" — and §5 keeps #314's one exception: after a control transfer the guest's components sit at the BOTTOM of the stack, where a tail peel would hand each player the other's hardware. That refusal names the way out rather than merely refusing.
func (*World) HasRelativeTarget ¶ added in v0.27.0
HasRelativeTarget reports whether the target slot holds something with a live relative state — a local craft or a remote ghost (v0.27 review follow-up). Every gate that used to spell Kind==TargetCraft for "can I do target-relative work" goes through here so ghost targets light up the same surfaces.
func (*World) HohmannDepartureWarning ¶ added in v0.10.1
HohmannDepartureWarning returns a non-empty advisory when the active craft's departure orbit is poorly conditioned for the coplanar circular Hohmann auto-plant to the body at targetIdx. Scoped to the intra-primary case (craft + target share a primary, e.g. LEO → Luna) — the path where the |R|-as-circular-radius and zero-inclination assumptions bite hardest. Returns "" for the heliocentric / moon-escape paths (out of scope for this guard) and whenever the geometry is within tolerance. v0.10.1+.
func (*World) HohmannPreviewFor ¶ added in v0.2.0
func (w *World) HohmannPreviewFor(bodyIdx int) HohmannPreview
HohmannPreviewFor computes a preview to the indicated body index in the current system. Picks the (µ, r1, r2) frame based on whether the target shares the craft's primary (intra-primary, e.g. LEO craft + Luna both around Earth) or sits in the heliocentric frame (Mars from LEO). v0.7.4+: previously the helper always used the system primary (Sun) and the craft's heliocentric distance — for moon targets that computed a Hohmann from the craft's solar distance (~150M km) to the moon's parent-relative semimajor axis (~384k km for Luna), giving nonsense Δv. Same flavor of bug fixed for PlanTransfer in v0.5.7.
func (*World) IterateBurnDV ¶ added in v0.8.6
IterateBurnDV refines the commanded Δv for a finite burn so the post-burn orbit's apsides match what an impulsive burn at the same commanded Δv would have produced. Newton-iterates against an RK4 finite-burn simulation (planner.IterateForTarget). Returns the refined Δv on success; falls back to dvGuess on iteration failure or for burn modes that don't have a meaningful apse target (BurnNormal±).
Target picked from mode:
- Prograde / Retrograde → match the impulsive apoapsis.
- RadialOut / RadialIn → match the impulsive periapsis.
- Normal± → no iteration (skip; PlanInclinationChange handles plane-rotation Δv compensation differently).
Limitation: iterates from the craft's *current* state, not the state at TriggerTime. For burns scheduled minutes-or-less ahead the state drift is negligible; for hours-ahead schedules the iteration is approximate. v0.8.6 (b).
func (*World) LiveSOIPass ¶ added in v0.18.0
LiveSOIPass computes the active craft's upcoming SOI pass from its live state, with no maneuver node required (ADR 0019 decisions A/B/C/E). This is the always-on, Target-independent pass the canvas draws bright and the SOI PASS chip reads when nothing is planted.
When the sibling scan finds nothing AND the craft sits inside a non-root body's SOI on a trajectory that leaves it, the in-SOI residence pass takes over (#157) — the encounter picture used to switch off at the exact moment of SOI entry, because the body had just become the primary and so stopped being a sibling.
func (*World) MissionFailFlash ¶ added in v0.21.0
MissionFailFlash returns the active failure-flash text and true while a just-failed mission is still within its flash window, or ("", false) once the window has elapsed (or none has failed). The player surface (the checklist chip) reads this each frame. v0.21 Slice 5 (ADR 0025 §5).
func (*World) MissionProgramEnabled ¶ added in v0.21.0
MissionProgramEnabled reports whether missions tagged with the given program are active. An untagged mission (program == "") is always active; an unconfigured World (nil set) treats every program as enabled. v0.21 Slice 7.
func (*World) NavballBasis ¶ added in v0.9.5
func (w *World) NavballBasis() (NavballBasis, bool)
NavballBasis returns the orthonormal basis for the active craft + current NavMode. ok=false when the basis is degenerate — zero velocity (NavOrbit), zero surface velocity (NavSurface, e.g. stationary on the launchpad before liftoff), missing or coincident target velocity (NavTarget), or a craft state with zero r / a rectilinear orbit (no defined orbital plane).
NavOrbit / NavTarget are velocity-framed (the sphere's pole is the orbital normal):
- EX (lat 0, lon 0): +v̂ (orbit) or +(v_active − v_target)̂ (target)
- EZ (lat +90): orbital normal (r × v)̂, re-orthogonalised against EX (target-prograde isn't generally ⟂ the orbit plane)
- EY = EZ × EX
NavSurface is a **local-horizon** sphere (KSP surface navball): the pole is the local vertical so the sky/ground hemispheres read true. This is velocity-independent, so it is well-defined on the launchpad (a craft sitting on the pad pointing radial-out reads at the sky pole, not the horizon band):
- EZ (lat +90, sky pole): local up = r̂
- EX (lon 0): local north
- EY = EZ × EX (= −local east)
so radial-out → lat +90 (zenith / sky), the horizon → lat 0, and the N/E/S/W compass ticks ring the lat-0 equator at their bearings.
func (*World) NavballMarkers ¶ added in v0.9.5
func (w *World) NavballMarkers() []render.NavballMarker
NavballMarkers returns the marker set the painter should overlay for the active craft + current NavMode. Each marker is already projected to (lat, lon) in the active basis — the painter only needs to forward-project to (dx, dy) and skip back-hemisphere hits.
Each marker direction is the unit vector that pressing that axis key would steer toward, resolved via ResolveAttitudeIntent + BurnDirectionWithTarget. So:
NavOrbit — orbit-frame prograde / retrograde / normal± / radial±
(six cardinals using the radial-diamond glyphs ◇ ◆)
NavSurface — prograde / retrograde swap to surface-relative
velocity; normal± / radial± stay orbit-frame
(matches ResolveAttitudeIntent's NavSurface fallthrough)
NavTarget — prograde / retrograde swap to target-relative velocity;
radial+ / radial- swap to BurnTarget / BurnAntiTarget
(toward / away from target) and use the target glyphs
◉ ◌ in target color so the swap is visible at a glance
This makes the marker set match the SAS hold semantics exactly: each glyph sits at the direction the corresponding axis key would aim. The disk center is always "where the craft is currently pointing" — when the player presses the prograde key and the SAS finishes settling, the prograde glyph and the disk center coincide.
Returns nil when the basis is unavailable. Individual markers are dropped when their direction is degenerate (zero surface velocity in NavSurface, coincident target velocity in NavTarget, etc.).
v0.9.5+.
func (*World) NavballSubObserver ¶ added in v0.9.5
NavballSubObserver returns (lat, lon, ok) on the navball for the active craft's nose direction (s.AttitudeMode → world-frame unit vector via BurnDirectionWithTarget) projected into the active NavMode's basis.
ok=false when the basis is degenerate or the craft has no defined nose direction (e.g. surface-prograde before liftoff). Caller degrades to a static / blank navball.
func (*World) NextFrameTransition ¶ added in v0.7.6
func (w *World) NextFrameTransition() (FrameTransition, bool)
NextFrameTransition returns the next upcoming frame transition implied by the planted maneuver-node chain, walking nodes in trigger-time order. Each node carries the primary's ID it was planted in; the first node whose PrimaryID differs from the running frame ID is the transition. Returns ok=false when no planted node changes frame, or when the craft is missing / the chain is empty / no resolved nodes exist.
The walk is intentionally cheap — no integration, no SOI math, just trusting the planner's PrimaryID labels. PlanMoonEscape and PlanHohmannTransfer both label arrival nodes in their target's frame, which is exactly what this surfaces. v0.7.6+.
func (*World) NodeInertialPosition ¶ added in v0.2.0
func (w *World) NodeInertialPosition(n ManeuverNode) orbital.Vec3
NodeInertialPosition returns the inertial (system-primary-centered) position where the node will fire. Forward-integrates the craft state from now to the node's trigger time using SOI-aware Verlet sub- stepping, then adds the OWNING primary's inertial position — the frame may differ from the craft's current primary if the trajectory crossed an SOI boundary.
Returns zero Vec3 if the craft is nil or the node is already past-due.
func (*World) NudgeLaunchZoom ¶ added in v0.11.0
NudgeLaunchZoom adjusts the player-pinned chase-cam scale in response to a `+/-` press. dir > 0 zooms in (×0.8), dir < 0 zooms out (×1.25), dir == 0 is a no-op. The first press from auto (LaunchZoom == 0) pins LaunchZoom to currentAutoScale BEFORE applying the multiplicative step — caller supplies the auto-scale because canvas-row knowledge lives in the screen layer, not the sim. Floor: 1.0 m/cell. No-op when there's no active craft. v0.11.0+ Slice 1.
func (*World) NudgeViewTiltPhi ¶ added in v0.18.3
NudgeViewTiltPhi adds delta degrees to ViewTilt.Phi and wraps the result into [0°, 360°) — spinning past either end keeps rotating rather than pinning, unlike Theta's clamp. Returns the resulting Phi so the caller can stamp it into a status flash. ADR 0021 G.
func (*World) NudgeViewTiltTheta ¶ added in v0.10.6
NudgeViewTiltTheta adds delta degrees to ViewTilt.Theta and clamps to [ViewTiltThetaMinDeg, ViewTiltThetaMaxDeg]. v0.10.6+. Returns the resulting Theta so the caller can stamp it into a status flash.
func (*World) PeerRange ¶ added in v0.33.0
func (w *World) PeerRange(p CoWarpPeer) (float64, bool)
PeerRange is the range from the viewer's anchor craft to the nearest of a peer's craft sharing its SOI primary — the Session roster's RANGE column. ok=false when there is no anchor or no same-primary craft to measure against, which the roster renders as a blank rather than a zero: a fabricated 0 m reads as "right next to you" (#297's lesson about zero values presented as facts).
func (*World) PerilunePosition ¶ added in v0.18.3
PerilunePosition returns the Perilune marker's canvas position under the Local-to-Body Arc rule (ADR 0021 B): the pass Body's CURRENT position plus the body-relative perilune offset — the same anchoring SegmentDrawPoints gives the arc, so the marker rides the drawn hyperbola. As arrival nears the Body's current position closes on its encounter position, so the marker converges to the true perilune.
func (*World) PlanCircularizeAtApoapsis ¶ added in v0.9.4
func (w *World) PlanCircularizeAtApoapsis() (*CircularizePlan, error)
PlanCircularizeAtApoapsis plants a prograde burn at the active craft's next apoapsis sized to circularise the orbit there (target periapsis = current apoapsis radius). Mirrors v0.9.3's "single-keystroke planter" pattern (auto-plant Hohmann via `H`, inclination match via `I`, rendezvous via `R` once that lands) applied to the ascent flow's natural last step.
Δv is computed analytically from vis-viva — the prograde difference between circular speed at apoapsis (sqrt(mu/r_apo)) and the orbit's along-track speed there (sqrt(mu·(2/r_apo − 1/a))). The integrator handles finite-burn loss at fire time using the existing planted-node burn pipeline; the impulsive Δv is within ~1-2% of the iterated finite-burn answer at S-IVB-class TWR (1+ in vacuum), enough to land the circularisation above the 200 km mission floor on most attempts.
Errors:
- ErrNoCraftForCircularize: no active craft.
- ErrCircularizeBelowAtmosphere: apoapsis is inside the primary's atmosphere (not a useful coast target). Player should keep burning the ascent profile to raise apoapsis first.
- ErrCircularizeBadOrbit: hyperbolic / degenerate state — the "next apoapsis" math doesn't converge.
v0.9.4+.
func (*World) PlanInclinationChange ¶ added in v0.7.4
func (w *World) PlanInclinationChange(targetIncl float64) (*planner.InclinationPlan, error)
PlanInclinationChange plants a single BurnPlaneChange maneuver node that rotates the craft's orbital plane to targetIncl (radians, in [0, π]). The burn fires at the next ascending or descending node, whichever comes sooner. The node carries the planner's signed rotation angle (PlaneChangeRad); the burn rotates the horizontal velocity through it about the radial axis, preserving |v| — see spacecraft.planeChangeDirection.
Returns the planner's InclinationPlan (Δv + chosen node) for HUD flashing; surfaces the planner's error untouched if the source orbit is equatorial / hyperbolic / already-at-target.
v0.7.4+. v0.10.4: a true plane change (was a pure orbit-normal burn, which over-sped the craft and left the orbit eccentric). Composes with v0.6.0's burn-at-next scheduler — the planted node uses an absolute TriggerTime (event resolver isn't needed since the planner already computed the future event time).
func (*World) PlanNode ¶ added in v0.2.0
func (w *World) PlanNode(n ManeuverNode)
PlanNode inserts a node into the active craft's Nodes slice, keeping the slice sorted by TriggerTime. Past-dated nodes are allowed — they fire on the next Tick. v0.8.1+: per-active-craft; the planted burn fires on the craft it was planted for, regardless of which craft the player is flying when it triggers.
func (*World) PlanPlaneMatch ¶ added in v0.10.4
func (w *World) PlanPlaneMatch(targetIdx int) (*planner.InclinationPlan, error)
PlanPlaneMatch plants a single BurnPlaneChange node that rotates the active craft's orbital plane to *coincide* with the orbital plane of the body at targetIdx — matching both inclination magnitude AND the line of nodes (Ω), so a subsequent Hohmann transfer to that body departs in the right plane.
PlanInclinationChange matches only the inclination *magnitude*: two orbits at equal inclination but different Ω are still tilted relative to each other (an equatorial LEO and the Moon's orbit, both read as ~19° in Earth's frame, sit ~25–39° apart). A Hohmann planned in the craft's plane then reaches the target's orbital radius far out of the target's plane and misses.
"Coplanar with the target" is exactly "zero inclination measured in a frame whose Z axis is the target's orbit normal" — so PlanPlaneMatch re-expresses the craft state in that frame and asks the existing inclination solver for inclination 0. The burn fires where the craft crosses the target plane and rotates by the full dihedral angle; the signed rotation is frame-invariant, so the resulting BurnPlaneChange node flies through unchanged.
Errors: ErrNoCraftForTransfer, errInvalidTransferTarget (bad index or a target with no orbit), and the planner's own errors surfaced untouched (already-coplanar / hyperbolic source). v0.10.4+.
func (*World) PlanRendezvousNudge ¶ added in v0.10.2
func (w *World) PlanRendezvousNudge() (*planner.RendezvousAdvisory, error)
PlanRendezvousNudge plants the recommended single-burn nudge as a new ManeuverNode on the active craft. Returns the advisory used so the caller can build a status flash; returns (nil, err) when the gate fails (no target, different primaries, no improvement, etc.).
TriggerTime = SimTime + leadBuffer, where leadBuffer is dynamic: max(rendezvousBurnLeadMin, nodeLeadSlack·angle/SlewRateRad + pad). This ensures v0.10.0 lead-compensated slew has room to converge even when the recommended axis is far from the current attitude. Event=TriggerAbsolute (immediate-style — fires at the computed time, no future event-relative resolution). TargetCraftIdx is captured one-based per the spacecraft.ManeuverNode convention so a later target switch does not retarget the planted burn.
v0.10.2+.
func (*World) PlanTransfer ¶ added in v0.3.1
func (w *World) PlanTransfer(targetIdx int) (*planner.TransferPlan, error)
PlanTransfer constructs a Hohmann auto-plant to the body at the given index in the active system and plants the resulting two-burn plan onto World.Nodes (departure + arrival). Returns the plan so callers can inspect Δv totals; returns nil and an error if the geometry is degenerate (target index invalid, target is the system primary, or craft state isn't ready).
Phasing is not enforced — the plan assumes ideal alignment, matching the v0.3.1 sandbox scope per designdocs/terminal-space-program/plan.md. Porkchop-plot polish for real launch windows is v0.3.2.
func (*World) PlanTransferAt ¶ added in v0.4.1
func (w *World) PlanTransferAt(targetIdx int, depDay, tofDay float64, opts TransferOptions) (*planner.TransferPlan, error)
PlanTransferAt constructs a Lambert-based transfer for a specific (departure-day, time-of-flight) pair — the cell selected on the porkchop plot — and plants the resulting two-burn plan onto World.Nodes. Parking and capture orbit parameters match PlanTransfer / PorkchopGrid so a cell's planted Δv equals the cell's scored Δv to within Lambert iteration tolerance.
depDay / tofDay are in days; depDay is an offset from w.Clock.SimTime. Used by the porkchop screen's Enter-to-plant path (v0.4.1).
func (*World) PlannedSOIPass ¶ added in v0.18.0
PlannedSOIPass is the dual-arc "planned" pass (ADR 0019 D, bright path): the SOI pass of the node-modified trajectory, scanned from the post-burn legs so the player sees the safe periapsis their burns produce against the no-burn Impact. Returns false with no node planted, or when the planned path reaches no SOI. TimeToPerilune is rebased to now — the legs begin when their node fires, in the future.
func (*World) PorkchopGrid ¶ added in v0.3.3
func (*World) PostBurnState ¶ added in v0.2.0
func (w *World) PostBurnState(n ManeuverNode) (physics.StateVector, string)
PostBurnState returns the craft's primary-relative state vector immediately after the given node would fire, plus the ID of the primary that frame is relative to. Forward-integrates SOI-aware to the trigger time, then applies the Δv in the node's direction mode. The PrimaryID return lets callers (OrbitView post-burn preview) correctly translate state.R into inertial coords when the burn fires in a frame other than the craft's home primary — critical for the v0.3.1 auto-plant arrival node, which fires heliocentrically (or in the destination SOI) by construction.
func (*World) PredictedFinalOrbit ¶ added in v0.6.1
func (w *World) PredictedFinalOrbit() (physics.StateVector, bodies.CelestialBody, bool)
PredictedFinalOrbit walks every planted node in trigger-time order and returns the craft state immediately after the last node fires, along with the primary body whose frame the state is relative to. ok=false when there are no planted nodes (or no craft) — caller should fall back to the live orbit.
Chaining semantics: start from the live craft state at clock time; for each node, propagate forward to the node's TriggerTime, apply the burn (impulsive Δv in the node's mode direction — finite-burn deformation is approximated as instantaneous since this is a HUD readout, not a flight integrator), then advance the running clock. Unresolved event-relative nodes are skipped — they'll resolve on a future tick and appear in subsequent renders.
SOI transitions during propagation are handled by the underlying integrator; bodies are snapshotted at the *current* clock time, so readouts on multi-day chains lose accuracy as planets move. That's fine for a glance-at-the-HUD reading; the planner's actual trajectory preview already has its own caveats around long horizons.
func (*World) PredictedLegs ¶ added in v0.6.1
func (w *World) PredictedLegs() []PredictedLeg
PredictedLegs walks every resolved planted node and returns one PredictedLeg per node, with the post-burn state expressed in the node's intended frame (PrimaryID, falling back to the propagated frame when unspecified). Returns nil during an active burn — the live state is mutating and chained predictions would flail (see PredictedFinalOrbit's same guard).
func (*World) PredictedSegmentsFrom ¶ added in v0.6.1
func (w *World) PredictedSegmentsFrom(post physics.StateVector, startPrimary bodies.CelestialBody, startClock time.Time, totalSeconds float64, samples int) []SOISegment
PredictedSegmentsFrom forward-integrates a post-burn state by totalSeconds and partitions the trajectory into SOISegments, parameterised on the starting primary and clock. Pre-v0.3.0 the predictor locked to the home primary's μ throughout, which made post-escape segments geometrically wrong even though their coloring was correct. v0.3.0: when a sub-step crosses a sphere-of-influence boundary, rebase the state vector to the new primary's frame and switch μ for subsequent steps. v0.6.1: used by the multi-leg colored preview, where each leg starts in its own node-planted frame (e.g. Hohmann departure leg in Earth, arrival leg in Mars). v0.8.4: takes a startClock so body positions track real time across the leg (per-sample refresh — sub-step refresh would cost 60 % of a render frame on long horizons), and folds atmospheric drag into the integrator via the active craft's EffectiveBallisticCoefficient. Output shape (a slice of SOISegments) is unchanged so the renderer keeps working.
func (*World) PredictedTargetApproach ¶ added in v0.17.3
func (w *World) PredictedTargetApproach() (TargetApproach, bool)
PredictedTargetApproach returns the active craft's closest approach to its current body Target, so the TARGET chip can show where the projected orbit passes the target — updating live as the player hand-flies a correction.
It scans two trajectories and prefers an actual SOI encounter (then the closest of those):
- The craft's CURRENT state forward over a horizon reaching past the furthest planted node. This is the trajectory the player is actually flying — and the load-bearing case, because once the departure burn fires the approach coast is no longer a PredictedLegs leg (it's folded into the propagation to the next node), so a legs-only scan goes blind exactly during the coast.
- The post-burn legs still in the craft's primary frame, so a transfer that's planted but not yet departed still previews its encounter.
ok=false when there's no body target sharing the craft's primary or no propagable trajectory.
func (*World) PreviewBurnState ¶ added in v0.6.1
func (w *World) PreviewBurnState(mode spacecraft.BurnMode, dv float64, duration time.Duration, event TriggerEvent) (physics.StateVector, bodies.CelestialBody, bool)
PreviewBurnState returns the craft state immediately after a hypothetical burn with the given (mode, dv, duration, event) parameters would fire — without mutating world state. Used by the maneuver-planner screen so its shadow trajectory + PROJECTED ORBIT readout reflect where the burn would *actually* fire, not where the craft is sitting right now.
For event != Absolute, the helper computes the time-of-flight to the event using the same orbital helpers as the lazy-freeze resolver, then propagates the craft forward via the SOI-aware integrator before applying Δv. Returns ok=false when the event is unreachable from the current orbit (hyperbolic, equatorial AN/DN, etc.) so the caller can fall back to a current-position preview.
Absolute event: dt is taken as zero — the absolute-time preview is always "burn applied at current state," which matches the planner's pre-v0.6 semantics. Real Absolute nodes fire at TriggerTime + Duration/2 in flight; the planner doesn't yet know which TriggerTime the user will choose, so previewing at "now" is the least-surprising default.
v0.6.3 polish: when duration > 0 the helper routes through `planner.SimulateFiniteBurn` so the preview reflects finite-burn deformation (off-tangential velocity rotation through the burn arc, finite-burn cosine loss, etc.) rather than the impulsive idealisation. The delivered Δv is also capped by the rocket- equation maximum the duration window allows — so a 400 m/s request with the form's default 10 s duration returns a preview reflecting only what 10 s of thrust would actually deliver (≈205 m/s for the S-IVB-1 default loadout), matching what the live integrator does when the burn terminates on duration rather than Δv.
func (*World) RCSActive ¶ added in v0.9.6
RCSActive reports whether the active craft is currently in RCS engine mode. Nil-safe (no craft → false), mirroring the CycleEngineMode guard. Used by the navball panel to colour the RCS toggle and by its click dispatch to toast the new state. v0.9.6-polish.
func (*World) RCSPuffs ¶ added in v0.8.0
func (w *World) RCSPuffs() []RCSPuffSample
RCSPuffs returns the recent pulses still within rcsPuffTTL of SimTime, in oldest-to-newest order. Each sample's Inertial position tracks the firing craft's CURRENT inertial position (v0.8.3+) rather than the position at fire time, so the visual stays anchored to the craft as it moves rather than drifting behind. Puffs whose firing craft has been removed from the slate (e.g. via Undock or DockCrafts) are dropped.
func (*World) RecommendedRendezvousBurn ¶ added in v0.10.2
func (w *World) RecommendedRendezvousBurn() (planner.RendezvousAdvisory, bool)
RecommendedRendezvousBurn returns the cached rendezvous advisory for the current active+target craft pair, recomputing on cache miss. Returns (_, false) when the advisory cannot be computed (no craft target, different primaries, or no active craft) so the TARGET HUD just hides the block.
The returned advisory is the same struct callers see from the underlying planner.RecommendRendezvousNudge. ok=true-with-advisory where advisory.Ok=false is the "computed, but no improvement available" path (advisory.Reason populated — "no useful nudge" or "docked") the HUD surfaces as a faint single-line tag; ok=false means the advisory couldn't be computed at all (no craft target, different primaries, degenerate state) and the HUD hides the block.
func (*World) RecomputeCommGraph ¶ added in v0.22.0
func (w *World) RecomputeCommGraph()
RecomputeCommGraph rebuilds w.CommGraph for the active craft's system: gathers ground-station + craft nodes with their current world positions and the body occluders, then runs connectivity. Called each Tick after physics; also lazily by CanCommandCraft if the cache is nil.
func (*World) RecordAction ¶ added in v0.21.0
RecordAction appends a semantic gameplay action to the per-tick event sink the mission evaluator drains (ADR 0025 §6/§7, v0.21). Called downward by the TUI input layer after it resolves a keybinding to an action — so events are rebinding-proof and `missions` never imports `tui`. No-op-safe to call when no mission is loaded (the sink is drained each tick anyway).
func (*World) RefinePlan ¶ added in v0.4.1
RefinePlan re-runs a heliocentric Lambert from the craft's current state to the destination body at the pending arrival node's TriggerTime, plants a mid-course correction burn at the current sim-time for Δv = |v1_lambert − v_craft_heliocentric|, and replaces the arrival node's Δv with |v2_lambert − v_target_heliocentric| via CaptureBurnDeltaV. Closes the porkchop / PlanTransfer loop by giving the player a way to correct drift during a coast.
Returns (correctionDv, refinedArrivalDv, error). err != nil if no pending arrival node exists (PlanTransfer / PlanTransferAt hasn't been called, or arrival already fired) or Lambert fails to converge.
The correction burn's mode (prograde vs retrograde) is picked by the sign of (v1_lambert − v_craft) · v_craft: aligned → prograde, else retrograde. This is a scalar approximation — full vector mid-course correction would need a new burn mode; for v0.4.1 scalar-along- velocity corrections are sufficient to close small drifts.
func (*World) RemoveCraftByID ¶ added in v0.28.0
func (w *World) RemoveCraftByID(id uint64) (*spacecraft.Spacecraft, bool)
RemoveCraftByID lifts the craft with the given stable ID out of the slate and returns it (v0.28 S5) — the guest side of a cross-player dock, where a player's craft leaves their own World to ride in the docker's stack. The active-craft index follows the removal so the player keeps flying a valid craft (or, if they removed their only craft while docking as guest, the index clamps and ActiveCraft() may be nil until they switch/undock). ok is false when no craft matches. The returned craft is detached — the caller hands it to the ledger; nothing in this World still references it.
func (*World) RendezvousApproachPhase ¶ added in v0.33.0
RendezvousApproachPhase is the exported form for the tui — the RENDEZVOUS chip's standing approach state and the copilot's warp-key semantics both fork on it.
func (*World) RendezvousCommit ¶ added in v0.29.0
func (*World) RendezvousInitiatorSeat ¶ added in v0.33.0
RendezvousInitiatorSeat reports whether the viewer holds the initiator seat of a live agreement — the bit that rides the wire so both sides agree on roles.
func (*World) RendezvousNeedsBurnToClose ¶ added in v0.34.0
RendezvousNeedsBurnToClose reports whether a committed CA is far enough above the couple/lock gate that a deliberate burn — not just riding waypoints — will be needed to actually close it (ADR 0039 S3). Called at Engage time on both sides (the initiator's own commit and the responder's join, which adopts the same τ/CA off the wire) so the choice to coast toward a wide encounter is visible up front rather than discovered after riding it for days.
func (*World) RendezvousRateHold ¶ added in v0.33.0
func (w *World) RendezvousRateHold() RendezvousRateHolder
RendezvousRateHold classifies what is setting the pair's rate right now — the chip's "held:" row, and the answer to "why do my warp keys do nothing" that #305 spent thirty minutes not having. Derived live against the viewer's own current selection, so it never lags a keypress.
func (*World) RendezvousSeatBurning ¶ added in v0.33.0
RendezvousSeatBurning reports whether the viewer's published seat rate comes from an active burn, so the partner's chip can name the cause.
func (*World) RendezvousSeatRate ¶ added in v0.33.0
RendezvousSeatRate is the viewer's published contribution to the pair's rate — what CraftReport carries and the partner clamps to. The initiator publishes their selected warp; the copilot publishes their brake (nothing while following); either folds in its own burn cap. 0 means this seat imposes no ceiling.
Deliberately a selection and not the post-clamp Effective warp: see the file header on why the acyclic derivation is what keeps #248 dead.
func (*World) RendezvousWarpEngaged ¶ added in v0.29.0
RendezvousWarpEngaged is the exported form for the tui (v0.29 S2) — the RENDEZVOUS chip forks its armed-waiting vs coasting state on it.
func (*World) ResetFocus ¶ added in v0.2.0
func (w *World) ResetFocus()
ResetFocus snaps back to the system-wide view. Also the coarse exit from Spectate (v0.28 S6) — [g] leaves a ghost focus like any other.
func (*World) ResolveAttitudeIntent ¶ added in v0.9.3
func (w *World) ResolveAttitudeIntent(intent AttitudeIntent) spacecraft.BurnMode
ResolveAttitudeIntent maps (intent, NavMode) → BurnMode. NavTarget silently falls back to NavOrbit when no craft target is bound — a stale NavTarget should never produce a zero-direction SAS hold. NavSurface only redefines prograde / retrograde (KSP behavior); the other axes stay orbit-relative.
func (*World) ResolveTargetCraft ¶ added in v0.14.5
func (w *World) ResolveTargetCraft() (*spacecraft.Spacecraft, int, bool)
ResolveTargetCraft is the exported form of targetCraft for the tui — resolves the world target cursor to its live craft + slate index, or ok=false for a non-craft / stale target (ADR 0012).
func (*World) ResolveTargetGhost ¶ added in v0.27.0
func (w *World) ResolveTargetGhost() (Ghost, bodies.CelestialBody, bool)
ResolveTargetGhost resolves a ghost target to its slate entry and SOI primary. ok=false when the target isn't a ghost or the ref is stale (owner gone, craft gone, other system).
func (*World) SegmentDrawPoints ¶ added in v0.18.3
func (w *World) SegmentDrawPoints(seg SOISegment, homeID string) []orbital.Vec3
SegmentDrawPoints returns the canvas plot positions for a predicted segment under the Local-to-Body Arc rule (ADR 0021 B): a segment in the craft's home SOI draws at its inertial sample positions, unchanged; a foreign-SOI segment draws each body-relative sample anchored at the owning body's CURRENT position, so the hyperbola wraps the body's drawn disk and converges to truth as arrival nears. Every foreign-SOI consumer — the live SOI Pass arc, the dim counterfactual, and the planted-node legs — routes through this one helper, so the pictures at one body can never disagree (the #66 two-site lesson, applied to drawing).
func (*World) SetActiveCraftIdx ¶ added in v0.9.3
SetActiveCraftIdx switches control to the craft at idx, syncing per-craft Target so each vessel keeps its own target binding across switches (v0.9.3 polish). Outgoing's live `w.Target` is checkpointed onto the outgoing craft, then `w.Target` is reloaded from the incoming craft's stored Target. NavMode is not yet per-craft (acceptable scope cap; revisit if NavMode drift across switches becomes friction).
Caller is responsible for bounds-checking idx against len(Crafts); out-of-range idx is treated as "no checkpoint, no load" so spawn paths that assign before any craft exists don't touch w.Target.
func (*World) SetAttitudeMode ¶ added in v0.7.3
func (w *World) SetAttitudeMode(mode spacecraft.BurnMode)
SetAttitudeMode updates the active craft's held attitude. If a manual burn is already in flight on that craft, the engine direction takes effect on the next tick. v0.8.1+: per-active-craft.
func (*World) SetEnabledMissionPrograms ¶ added in v0.21.0
SetEnabledMissionPrograms replaces the set of active mission programs — the tui pushes this from the persisted Tutorial/Challenges toggles (ADR 0025 §2 / v0.21 Slice 7). A nil map is treated as "all programs enabled" (the back-compat default); pass an empty non-nil map to disable every tagged program (a fresh sandbox with nothing opted in).
func (*World) SetTargetBody ¶ added in v0.9.0
SetTargetBody sets the body target by system index. Out-of-range or system-primary (idx 0) selections clear the target — neither is a valid Hohmann / plane-match consumer.
func (*World) SetTargetCraft ¶ added in v0.9.0
SetTargetCraft sets the craft target by slate index, storing the craft's stable ID (ADR 0012) so the binding survives slate shifts. The active craft can't target itself; out-of-range or self-targeting clears.
func (*World) SetTargetGhost ¶ added in v0.27.0
SetTargetGhost aims the active craft at a remote player's craft (v0.27 S6). The Session screen is the selection surface.
func (*World) SetThrottle ¶ added in v0.7.3
SetThrottle clamps the requested throttle to [0, 1] and applies it to the active craft. Setting throttle to 0 also stops the active craft's in-flight manual burn so the "x = cut" muscle memory works in one keypress; a normally-running planted burn keeps running, but a STALLED planted burn (paused waiting for a stage, producing no thrust) is aborted — that's the only way to abandon a transfer the spent stage couldn't finish, and without it the dangling burn would block StartManualBurn (v0.12.x pause-and-resume).
func (*World) SetViewModeLaunch ¶ added in v0.11.4
func (w *World) SetViewModeLaunch()
SetViewModeLaunch (v0.11.4+, ADR 0004) is the manual-jump path for the `V` (shift+v) keybinding: short-circuits the lowercase `v` cycle and drops the player into ViewLaunch focused on the active vessel. Stashes the prior ViewMode into PrevViewMode (so a switch-end release can restore it) and opens a session — same surface as routeToLaunchView, just player-initiated rather than auto-routed. Leaving is a manual `v` cycle (ADR 0021 D). No active vessel is not a precondition; the LaunchView.Render path covers the nil-active case (sub-scope 5).
func (*World) SpawnCraft ¶ added in v0.8.2
func (w *World) SpawnCraft(spec SpawnSpec) (*spacecraft.Spacecraft, error)
SpawnCraft adds a new craft to the slate using the given spec. The new craft is placed in a circular orbit at the requested altitude, 90° around the parent body from the active craft's position (or +Y if no offset is meaningful). After spawn the new craft becomes active. v0.8.2+.
func (*World) SpawnSisterCraft ¶ added in v0.8.1
func (w *World) SpawnSisterCraft() (*spacecraft.Spacecraft, error)
SpawnSisterCraft is the auto-pick variant of SpawnCraft: it delegates with an empty SpawnSpec, which round-robins the loadout cycle. Kept as a convenience for tests / the v0.8.1 rapid-spawn behaviour. v0.8.2+: the orbit screen routes through a SpawnCraft form for explicit loadout pick.
func (*World) SpectateGhost ¶ added in v0.28.0
SpectateGhost enters Spectate mode on a remote player's ghost (v0.28 S6, ADR 0034): the camera fits once to the ghost's drawn orbit extent (a Framing Event) then tracks the ghost as focus, pan/zoom free and no re-fit on report corrections. Read-only — it adds no write surface, so it's reachable by host and guest alike. The Session screen is the selection surface; the ref is validated lazily at render (a vanished ghost degrades to the system view via FocusPosition/FocusZoomRadius).
func (*World) StageActive ¶ added in v0.9.1
StageActive jettisons the bottom stage (Stages[0]) of the craft at craftIdx, spawning it as a passive Spacecraft at the active craft's exact inertial position + velocity. The jettisoned stage carries any residual fuel + monoprop. The active craft is rebuilt from Stages[1:] — the new bottom (Stages[0] post-shift) becomes the firing engine.
Returns (newActiveIdx, jettisonedIdx, nil) on success. Both indices are slate-relative after the spawn; jettisonedIdx is the position of the dropped-stage craft. newActiveIdx is unchanged from the input craftIdx since the active craft stays in place (the jettisoned stage is appended to the slate at the end).
No-op (returns ErrStageOnlyOne) when the craft has exactly one stage — preserves the player's "core" craft. This matches KSP's "you can't decouple your last command pod" intuition. The HUD surfaces this via a status flash on the `space` keypress.
v0.9.1+.
func (*World) StartManualBurn ¶ added in v0.7.3
func (w *World) StartManualBurn()
StartManualBurn opens the active craft's engine in its current AttitudeMode at its current Throttle. No-op if a planted ActiveBurn is already in flight on the active craft (planted burns own the engine until they complete), fuel is empty, or engine is in RCS mode. Idempotent.
v0.8.1+: per-active-craft. Each craft owns its own ManualBurn — switching active craft mid-flight does not move the in-flight engine to the new craft.
func (*World) StepRendezvousBrake ¶ added in v0.33.0
StepRendezvousBrake moves the copilot's brake one rung — down brakes the pair, up releases it back toward following (ADR 0037 §2). Returns the new brake factor (0 while following) and whether the press applied at all: it refuses outside the copilot seat, which is what makes "never push it faster" a property of the input layer rather than a convention.
Braking from FOLLOWING starts one rung below whatever the pair is actually doing, so the first press is a real slowdown regardless of where the initiator had the clock; releasing past the top rung returns to following rather than pinning a ceiling the initiator can't exceed anyway.
func (*World) StopManualBurn ¶ added in v0.7.3
func (w *World) StopManualBurn()
StopManualBurn cuts the active craft's manual burn. No-op when no manual burn is in flight on the active craft.
func (*World) SwitchToCraftIdx ¶ added in v0.12.0
SwitchToCraftIdx jumps control directly to the craft at idx (0-based), backing the numbered craft-slot keys (1..9 → idx 0..8). Unlike CycleActiveCraft it does not wrap: a slot key with no craft behind it (idx ≥ len(Crafts)) or a request for the already-active craft is a no-op. Returns true only when the active craft actually changed. Mirrors CycleActiveCraft's StopManualBurn so the newly-selected craft starts in a known engine state. v0.12.0+.
func (*World) TargetName ¶ added in v0.9.0
TargetName returns a short human label for the current target, suitable for the TARGET HUD block. Empty string when no target is set or the index is stale.
func (*World) TargetState ¶ added in v0.9.0
TargetState resolves the current target to its inertial state in the system primary's frame (heliocentric for Sol). Returns ok=false when Target.Kind is TargetNone, the index is stale, or the craft doesn't share enough state to surface (a non-active craft's inertial position is built from its primary plus its primary- relative R, the same way CraftInertial does for the active craft).
Used by the rendezvous-tooling slice (v0.9.3) for closest-approach computation; v0.9.0 callers limit themselves to the body case but the craft branch ships now so consumers don't need to special-case the API surface later.
func (*World) TargetStateRelativeToActivePrimary ¶ added in v0.9.3
TargetStateRelativeToActivePrimary returns the target craft's state expressed in the active craft's primary-relative frame, so the same (R, V) basis as ActiveCraft().State can be used for relative-vector math (closest approach, target-prograde direction, |v_rel|, range). Returns ok=false when no craft target is set, the index is stale, or there is no active craft.
Same-primary case (the common one — rendezvous in LEO): both craft share a primary, so the target's primary-relative state is already in the active's frame. Cross-primary case: convert via inertial, subtract the active primary's pose. v0.9.3+.
func (*World) Tick ¶
func (w *World) Tick()
Tick advances sim-time one base step (scaled by warp factor) and integrates the spacecraft with velocity-Verlet sub-stepping so each sub-step is < 1/100th of the current orbital period.
func (*World) ToggleAutoWarp ¶ added in v0.16.0
ToggleAutoWarp engages the driver, or disengages it if already on (a manual cancel — Selected Warp is left untouched). Returns the engaged state after the toggle.
func (*World) ToggleInstantSAS ¶ added in v0.10.0
func (w *World) ToggleInstantSAS()
stepThrust advances one RK4 sub-step with engine thrust, debits the active-burn Δv budget by the analytical thrust contribution (Thrust×Throttle/mass × dt), and burns fuel via the configured mass flow. Dispatches between ActiveBurn (planted node, fixed mode + throttle captured at fire-time) and ManualBurn (v0.7.3+, mode and throttle driven by live World.AttitudeMode + Craft.Throttle).
v0.7.6+: planted burns honour their per-node throttle rather than the live craft setting, so the player can tweak the throttle knob during a coast without slowing an in-flight planted burn. ToggleInstantSAS flips the legacy instant-attitude opt-out. Default (false) is rate-limited slew (v0.10.0+); true restores the pre-v0.10 instant snap. Session UI preference — not persisted (see the InstantSAS field doc).
func (*World) ToggleManualBurn ¶ added in v0.7.4
func (w *World) ToggleManualBurn()
ToggleManualBurn engages or disengages the active craft's manual burn. v0.7.3.2+ explicit-engage gate.
func (*World) Transpose ¶ added in v0.12.6
Transpose performs the Apollo transposition (ADR 0009) on the craft at craftIdx in one shot: it reproduces the end-state of the manual docking flip — the SM becomes the firing core (Stages[0]) with the LM as a releasable nose payload — without flying the rendezvous.
Precondition: the craft is exactly [Descent, Ascent, SM, CM] — the state left after the three Saturn stages have decoupled (the first three entries of the loadout's [1,1,1,2] DecouplePlan; the trailing 2 drops the LM for the manual flip instead). Otherwise returns ErrTransposeNotReady.
It splits the stack into the LM (Descent + Ascent) and the CSM core (SM + CM), reorders the live craft to [SM, CM, Descent, Ascent] so the SM's SPS is the firing engine (SyncFields mirrors Stages[0]) for LOI and TEI, and registers BOTH halves as DockedComponents carrying their per-stage breakdown. The LM then peels off as a 2-stage craft via the existing Undock (docking.go) for the lunar descent; the SM/CM core survives. This is the docking machinery already in use, not new flight semantics — DockCrafts would build the identical composite from a hand-flown flip.
func (*World) Undock ¶ added in v0.8.3
Undock splits the craft at idx back into its DockedComponents, removing the composite from the slate and inserting one craft per component. Each restored craft inherits a share of the composite's current Fuel + Monoprop pools, prorated by its pre-dock capacity. Restored craft sit near the composite's current position, separated by a small offset so they don't immediately re-dock; their velocities pick up a tiny relative push (a "spring release") so they drift apart. v0.8.3+.
No-op when the craft has no DockedComponents (i.e. wasn't a composite). Active idx tracks to the first restored component so the player keeps flying the most-recently-active vessel's identity. The composite's Nodes / ActiveBurn / ManualBurn / AttitudeMode / EngineMode are dropped — they were tied to the composite, which no longer exists.
func (*World) UndockGuest ¶ added in v0.28.0
func (w *World) UndockGuest(compositeIdx int, guestOwner string, guestCraftID uint64) (*spacecraft.Spacecraft, bool)
UndockGuest splits the guest's sub-stack out of the composite at compositeIdx and RETURNS the restored guest craft (NOT added to this World — the serve layer injects it into the guest's own World at the matching seam). The docker's composite shrinks in place, keeping its current flight (no active switch, no burn stop — like Deploy, since the composite persists). Reverts to a plain craft when only the docker's own components remain.
The restored craft is placed at the composite's CURRENT state (r, v) and stamped with the guest's original stable ID (guestCraftID), so it returns to the guest exactly where the stack is — "times always match at the seam" (ADR addendum: this is what keeps undock-anytime sound). guestOwner selects which components peel off; guestCraftID both stamps the return identity and, when non-zero, disambiguates one guest among several sharing an owner (not reachable in the 2-party MVP).
Returns ok=false when the indices are invalid, or when the guest's sub-stack isn't a peelable top block — see guestTopBlock for what that means and why a refusal beats a mis-slice (#307). The caller must SAY SO rather than drop a refusal on the floor: silence here reads as a broken key from the guest's seat. MVP limitation: a guest that docked a multi-component composite is rebuilt as a single multi-stage craft from its first component's identity — the sub-composite seams are not preserved (passive-station posture docks a single craft; noted for playtest).
func (*World) UndockRefusal ¶ added in v0.33.0
UndockRefusal says, in the player's words, why Undock(idx) will refuse — or "" when it will split the composite. It is exhaustive over Undock's false paths BY CONSTRUCTION: Undock consults it instead of re-deriving the conditions, so the two cannot drift and a refused `U` can never be silent (#308 — live, a legitimate refusal read as "undock is broken" and cost an evening of diagnosis from the wrong premise).
Each string names the situation and, where there is one, the way out. The caller renders it verbatim.
Source Files
¶
- auto_warp.go
- chat.go
- clock.go
- commnet.go
- commnet_band.go
- cowarp.go
- craft_identity.go
- docking.go
- docking_guest.go
- end_flight.go
- focus.go
- ghost.go
- ground_stations.go
- handback.go
- hohmann.go
- hohmann_guard.go
- landed.go
- launch_anchor.go
- launch_sites.go
- launch_trail.go
- lifecycle.go
- lunar_start.go
- maneuver.go
- nav.go
- navball.go
- node_identity.go
- predict.go
- rcs.go
- rendezvous.go
- rendezvous_seat.go
- session_info.go
- soipass.go
- spawn.go
- staging.go
- start_scenario.go
- target.go
- tick.go
- view.go
- view_launch.go
- world.go