sim

package
v0.5.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var WarpFactors = []float64{1, 10, 100, 1000, 10000, 100000}

WarpFactors are the discrete time-warp steps per docs/plan.md §Time-warp UX.

Functions

func TickCmd

func TickCmd(d time.Duration) tea.Cmd

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.

Types

type ActiveBurn added in v0.2.1

type ActiveBurn struct {
	Mode        spacecraft.BurnMode
	DVRemaining float64
	EndTime     time.Time
	PrimaryID   string
}

ActiveBurn is the runtime state of an in-progress finite burn. Set by executeDueNodes when a node with Duration>0 fires; cleared by the integrator when DVRemaining hits zero or SimTime passes EndTime. PrimaryID is propagated from the originating ManeuverNode (empty for legacy nodes) — diagnostic only; the integrator always works in the craft's current primary frame.

type Clock

type Clock struct {
	SimTime  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 NewClock

func NewClock(start time.Time, baseStep time.Duration) *Clock

NewClock starts a clock at the J2000 epoch + 0 days, warp 1×.

func (*Clock) Advance

func (c *Clock) Advance()

Advance moves SimTime forward by BaseStep × warp. Called once per tick.

func (*Clock) TogglePause

func (c *Clock) TogglePause()

TogglePause flips the paused state.

func (*Clock) Warp

func (c *Clock) Warp() float64

Warp returns the current multiplier.

func (*Clock) WarpDown

func (c *Clock) WarpDown()

WarpDown steps down one level (saturates at 1×).

func (*Clock) WarpUp

func (c *Clock) WarpUp()

WarpUp steps up one warp level (saturates at max).

type Focus added in v0.2.0

type Focus struct {
	Kind    FocusKind
	BodyIdx int
}

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
)

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
}

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 ManeuverNode added in v0.2.0

type ManeuverNode struct {
	TriggerTime time.Time
	Mode        spacecraft.BurnMode
	DV          float64
	Duration    time.Duration
	PrimaryID   string
}

ManeuverNode represents a planned burn. v0.5.14+: TriggerTime is the burn-CENTER moment (the planner's intended firing point), not the burn start. For impulsive burns (Duration=0) center == start == TriggerTime. For finite burns the integrator actually starts the burn at TriggerTime - Duration/2 so the burn is centered on TriggerTime. The HUD displays TriggerTime as "T+(burn moment)" so the player sees the planner's intent, not the implementation start.

Duration controls finite vs impulsive: zero = instant Δv (legacy v0.1 path); non-zero = sustained engine burn lasting up to Duration or until DV is delivered, whichever first. Finite-burn execution is driven by World.ActiveBurn during subsequent ticks.

PrimaryID is the body whose frame the burn was planned in (empty = the craft's home primary at plant time, which is the v0.1 default and keeps legacy nodes working). Auto-plant transfers (v0.3.1) plant a geocentric departure plus a heliocentric arrival; PrimaryID lets the planner UI render a frame-distinct glyph and lets the burn- execution layer warn if a node fires in an unexpected frame.

func (ManeuverNode) BurnEnd added in v0.5.10

func (n ManeuverNode) BurnEnd() time.Time

BurnEnd returns the sim-time at which the integrator should terminate this node's burn (regardless of Δv-remaining or fuel state). For impulsive nodes BurnEnd equals TriggerTime. v0.5.14+.

func (ManeuverNode) BurnStart added in v0.5.10

func (n ManeuverNode) BurnStart() time.Time

BurnStart returns the sim-time at which the integrator should fire this node's burn. For impulsive nodes (Duration=0) BurnStart equals TriggerTime. For finite nodes BurnStart is `TriggerTime - Duration/2` so the burn is centered on TriggerTime. v0.5.14+.

type SOISegment added in v0.2.0

type SOISegment struct {
	PrimaryID string
	Points    []orbital.Vec3 // inertial, system-primary-centered
}

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.

type TickMsg

type TickMsg time.Time

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 World

type World struct {
	Systems    []bodies.System
	SystemIdx  int
	Calculator orbital.Calculator
	Clock      *Clock

	// Craft is the player vessel. Spawns around Earth in Sol at startup.
	// Nil when no primary is loaded (unreachable in v0.1).
	Craft *spacecraft.Spacecraft

	// Focus selects what the OrbitView canvas is centered on. Zero value
	// (FocusSystem) matches v0.1.0 behavior.
	Focus Focus

	// Nodes holds planned burns, sorted by TriggerTime. Each fires
	// automatically when Clock.SimTime reaches its trigger.
	Nodes []ManeuverNode

	// ActiveBurn is non-nil while a finite-duration burn is mid-execution.
	// Set by executeDueNodes when a Duration>0 node fires; cleared by
	// integrateSpacecraft when DVRemaining hits zero or SimTime ≥ EndTime.
	ActiveBurn *ActiveBurn
	// 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

func NewWorld() (*World, error)

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) 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. Primary (index 0) is anchored at origin. Bodies with ParentID set (moons; v0.5.0+) are resolved recursively as parent_position + position-relative-to- parent, so Luna sits in Earth's frame, Io in Jupiter's, etc.

func (*World) ClearNodes added in v0.2.0

func (w *World) ClearNodes()

ClearNodes wipes every pending node.

func (*World) CraftInertial

func (w *World) CraftInertial() orbital.Vec3

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) CraftTrail added in v0.5.2

func (w *World) CraftTrail() []orbital.Vec3

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

func (w *World) CraftVisibleHere() bool

CraftVisibleHere reports whether the spacecraft should be drawn in the currently-viewed system. v0.1 Craft lives in Sol only.

func (*World) CycleFocus added in v0.2.0

func (w *World) CycleFocus(forward bool)

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) CycleSystem

func (w *World) CycleSystem()

CycleSystem advances to the next system (wraps). Recreates the calculator. Spacecraft does not follow — remains in Sol per plan §MVP scope. Resets focus to system-wide because body indices don't carry across systems and the craft is only visible in Sol.

func (*World) EffectiveWarp

func (w *World) EffectiveWarp() float64

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) FocusName added in v0.2.0

func (w *World) FocusName() string

FocusName returns a short human label for the current focus.

func (*World) FocusPosition added in v0.2.0

func (w *World) FocusPosition() orbital.Vec3

FocusPosition returns the inertial (system primary-centric) position of the current focus target. Origin for FocusSystem.

func (*World) FocusZoomRadius added in v0.2.0

func (w *World) FocusZoomRadius() float64

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.

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. Uses the system primary's GM, the craft's current inertial distance as r1, and the target's semimajor axis as r2.

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) PlanNode added in v0.2.0

func (w *World) PlanNode(n ManeuverNode)

PlanNode inserts a node into World.Nodes, keeping the slice sorted by TriggerTime. Past-dated nodes are allowed — they fire on the next Tick.

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 docs/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) (*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) PorkchopGrid added in v0.3.3

func (w *World) PorkchopGrid(targetIdx int, depDays, tofDays []float64) ([][]float64, error)

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.

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) PredictedSegments added in v0.2.0

func (w *World) PredictedSegments(post physics.StateVector, totalSeconds float64, samples int) []SOISegment

PredictedSegments forward-integrates a post-burn state by totalSeconds and partitions the trajectory into SOISegments. 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. Output shape (a slice of SOISegments) is unchanged so the renderer keeps working.

Body positions are still snapshot at Clock.SimTime — accurate for short horizons relative to target body orbital period; an approximation flagged in commit history for interplanetary horizons.

func (*World) RefinePlan added in v0.4.1

func (w *World) RefinePlan() (correctionDv, arrivalDv float64, err error)

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) ResetFocus added in v0.2.0

func (w *World) ResetFocus()

ResetFocus snaps back to the system-wide view.

func (*World) System

func (w *World) System() bodies.System

System returns the currently active system.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL