screens

package
v0.11.4 Latest Latest
Warning

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

Go to latest
Published: May 27, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package screens implements the individual tea.Model screens composed by tui.App: OrbitView (C8), BodyInfo (C9), Maneuver (C20), Help (C9).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BodyPixelRadius added in v0.3.2

func BodyPixelRadius(b bodies.CelestialBody, isPrimary bool, scale float64, maxPx int) int

BodyPixelRadius returns the body's render radius in pixels. When the canvas is zoomed in enough that the body's true radius projects to at least trueSizeThreshold pixels, render at true size — so the rendered disk represents real surface, and a periapsis marker inside it visually reads as a collision. When zoomed out, fall back to a tier bucket so the body stays visible (true would be sub-pixel at system-wide zoom).

Pass scale=0 to force the tier-bucket path (used when projection metadata isn't available).

The `isPrimary` flag promotes a small body to star tier in the fallback path so the system primary always renders bigger than its planets even when its physical radius wouldn't otherwise put it there.

Types

type BodyInfo

type BodyInfo struct {
	// contains filtered or unexported fields
}

BodyInfo is the full-screen detail view for a single celestial body. Entered via `i` from OrbitView; `esc` (handled at App level) returns.

func NewBodyInfo

func NewBodyInfo(th Theme) *BodyInfo

func (*BodyInfo) Render

func (b *BodyInfo) Render(w *sim.World, selectedIdx, cols, rows int) string

Render displays the selected body's physical and orbital data.

type BurnExecutedMsg

type BurnExecutedMsg struct {
	Mode        spacecraft.BurnMode
	DV          float64
	Event       sim.TriggerEvent
	TriggerTime time.Time
	EditingIdx  int // -1 = creating a new node; ≥ 0 = replacing world.Nodes[idx]
	// Throttle (v0.7.6+) is the per-node throttle [0, 1]. Zero is
	// remapped to 1.0 by ManeuverNode.EffectiveThrottle, so callers
	// that don't set it (legacy quick-plant paths) get the prior
	// full-open behaviour for free.
	Throttle float64
	// IterateForTarget (v0.8.6 (b)) requests that the app refine the
	// commanded Δv via World.IterateBurnDV before planting, so the
	// post-burn apsides match what an impulsive Δv at the same
	// commanded value would have delivered (compensating finite-burn
	// loss). Ignored for impulsive (zero-thrust) and Normal± burns.
	IterateForTarget bool
	// TargetCraftIdx (v0.9.3+) is the one-based encoding of the
	// target slate idx the form was bound to at plant. Zero = no
	// target. Mirrors ManeuverNode.TargetCraftIdx; the app passes it
	// straight through. Only populated for target-relative modes /
	// TriggerNextClosestApproach event.
	TargetCraftIdx int
}

BurnExecutedMsg is emitted when the user hits Enter. App consumes it. Event (v0.6.0+) selects the trigger model — TriggerAbsolute uses the app-side default delay; event-relative modes leave TriggerTime zero and let the World's lazy-freeze resolver compute it from the live orbit on the first Tick after plant.

v0.6.4+: TriggerTime non-zero forces the app to plant a real ManeuverNode at exactly that time (skipping the legacy "fire now" path used by quick-plant). Set by LoadNode so a click-to-edit flow preserves the original schedule. EditingIdx ≥ 0 tells the app to remove the original Nodes[idx] before planting, so the edit reads as "replace in place" rather than "duplicate."

v0.6.5: Duration dropped from this message — the App computes it on receipt via spacecraft.BurnTimeForDV(DV) using the live craft's thrust + Isp + mass. Letting the player set both Δv AND duration was over-determined: at fixed thrust + mass the two are the same dial, and the only effect of mismatch was a truncated burn (planned Δv undelivered if the duration was too short). Zero-thrust craft return Duration = 0 from BurnTimeForDV, preserving the impulsive code path even though the form no longer exposes it.

type Help

type Help struct {
	// contains filtered or unexported fields
}

Help is the keybinding reference overlay. Invoked via `?` from any screen; `?` or `esc` returns.

func NewHelp

func NewHelp(th Theme) *Help

func (*Help) Render

func (h *Help) Render() string

type LaunchSitePreset added in v0.9.2

type LaunchSitePreset struct {
	Name        string
	LatitudeDeg float64
	// LongitudeEastDeg is east-positive longitude in degrees relative
	// to the body's prime meridian at simTime=0 (our pseudo-Greenwich
	// convention — see SpawnSpec.LongitudeOffset doc).
	LongitudeEastDeg float64
}

LaunchSitePreset bundles a named real-world launch site with its real-world latitude + longitude (east-positive, relative to Earth's prime meridian). v0.9.2+. The form's LATITUDE field cycles through these so picking "Cape Canaveral" lands the craft on KSC LC-39A, not just at "the right latitude."

type LaunchView added in v0.11.0

type LaunchView struct {
	// contains filtered or unexported fields
}

LaunchView (v0.11.0+) is the ViewLaunch chase-cam screen — a sibling of OrbitView, not an extension. ADR-0002 covers the call. Minimal chrome: title, canvas with the chase-cam scene, footer with key hints; no sidepanel (the orbit screen's body list / target panel are irrelevant during a launch). The HUD launch-readout strip is overlaid on the bottom braille row of the canvas (same precedent as the orbit screen's status overlay).

func NewLaunchView added in v0.11.0

func NewLaunchView(th Theme, hudSource *OrbitView) *LaunchView

NewLaunchView constructs the chase-cam screen, paired with the supplied OrbitView so the right-side HUD column (VESSEL / PROPELLANT / ATTITUDE / LAUNCH / NAVBALL) reuses the OrbitView's renderers verbatim. Playtest (Slice 1.6) showed the no-sidepanel experiment from the original plan dropped too many readouts (altitude, stage fuel, horizontal velocity) for the launch view to stay flyable; reusing the orbit HUD keeps the chrome legible.

func (*LaunchView) CurrentScale added in v0.11.0

func (v *LaunchView) CurrentScale(w *sim.World) float64

CurrentScale returns the metres-per-cell scale the chase-cam is currently rendering at — either the player-pinned w.LaunchZoom (when non-zero) or the auto-altitude-driven default sized to this view's canvas. Callers (App's `+/-` handler) pass it to World.NudgeLaunchZoom so the first nudge from auto pins the pre-render scale rather than a hardcoded constant.

func (*LaunchView) Render added in v0.11.0

func (v *LaunchView) Render(w *sim.World, totalCols, totalRows int) string

Render builds the chase-cam frame for the current world state. Slice 1 chrome: title + canvas + footer; the canvas carries the horizon curve + SurfaceColor fill + pad marker + breadcrumb trail + active-vessel glyph; the HUD launch-readout strip is overlaid on the bottom braille row.

func (*LaunchView) Resize added in v0.11.0

func (v *LaunchView) Resize(totalCols, totalRows int)

Resize sizes the canvas to leave the right ~30% for the HUD column (mirrors OrbitView's split so the two screens line up when cycling). Reserve 4 rows for title + footer + border.

type Maneuver

type Maneuver struct {
	// contains filtered or unexported fields
}

Maneuver is the burn-planning screen. Opening it pauses the sim (app.go handles the pause); closing with Esc cancels, with Enter emits a BurnExecutedMsg that the app applies to the spacecraft.

Per plan §C20: live preview = shadow trajectory on a miniature canvas. v0.2.1: three fields — mode / Δv / duration. v0.6.0: four fields — mode / fire-at / Δv / duration. v0.6.5: three fields again — mode / fire-at / Δv. Duration is no longer an independent input; the planner derives it from Δv via the rocket equation at commit time, since at fixed thrust + mass the two are the same dial — letting the player set both was over-determined and the only effect of mismatch was a truncated burn (planned Δv undelivered if duration was too short). KSP-style: specify Δv, the engine takes as long as it takes.

func NewManeuver

func NewManeuver(th Theme) *Maneuver

func (*Maneuver) HandleKey

func (m *Maneuver) HandleKey(msg tea.KeyMsg) (tea.Cmd, bool)

HandleKey routes planner-local keys. Returns (cmd, done) where done=true means the app should exit the maneuver screen (commit or cancel).

Key bindings:

tab / shift+tab        — cycle focus across mode / fire-at / Δv fields
←/→ (mode focused)     — cycle direction modes
←/→ (fire-at focused)  — cycle trigger events (Absolute / NextPeri / NextApo / NextAN / NextDN)
enter                  — commit burn → emits BurnExecutedMsg with rocket-equation duration
esc                    — cancel → plain exit (app handles)
ctrl+d                 — delete the planted node being edited (no-op when creating new)
c / C (or ctrl+k)      — clear ALL planted nodes for the active craft
digits/backspace       — forwarded to focused text input

func (*Maneuver) LoadNode added in v0.6.4

func (m *Maneuver) LoadNode(idx int, n sim.ManeuverNode)

LoadNode pre-populates the form fields from an existing planted node and records the click-to-edit state — used by the v0.6.4 orbit-canvas mouse path. Maps the node's BurnMode + TriggerEvent back to their cycle indices, writes Δv / duration into the text inputs, and stores idx + TriggerTime so the next Enter commit emits a BurnExecutedMsg with EditingIdx = idx + TriggerTime = original schedule. The app then removes Nodes[idx] before planting so the edit replaces in place AND preserves the node's future trigger time.

func (*Maneuver) LoadStaged added in v0.6.4

func (m *Maneuver) LoadStaged(triggerTime time.Time)

LoadStaged opens the form for a NEW node staged at a specific trigger time — used by the v0.6.4 empty-canvas mouse path to "click a point on the orbit, plant a burn there." Distinct from LoadNode in that there's no original to replace (editingIdx stays at -1); the form simply previews and commits with the staged TriggerTime so the new node fires at the click's projected orbit position. Mode / fire-at fall back to defaults (prograde / Absolute); Δv defaults to "100" so the form is immediately usable, focus jumps to the Δv field so the player can type a value without tabbing.

func (*Maneuver) Render

func (m *Maneuver) Render(w *sim.World, cols, rows int) string

Render composes the preview canvas + form panel.

func (*Maneuver) ResetEditing added in v0.6.4

func (m *Maneuver) ResetEditing()

ResetEditing clears the click-to-edit state so the next commit plants a fresh node rather than replacing one. Called on `m`-key open (new-node intent) and after every BurnExecutedMsg / Esc so the editingIdx doesn't leak across opens.

func (*Maneuver) Resize

func (m *Maneuver) Resize(cols, rows int)

Resize handles terminal-size changes. Keep the maneuver canvas ≤ 60 cols wide so the form panel sits cleanly alongside it.

func (*Maneuver) SetTargetCraft added in v0.9.3

func (m *Maneuver) SetTargetCraft(ok bool, idx int)

SetTargetCraft binds (or unbinds) the target-craft slate index the form's planted burn will be aimed at. Called by the app when opening the form so the four target-relative burn modes and the TriggerNextClosestApproach event can resolve at plant + fire time. Pass ok=false to clear (no craft target set / target is a body). v0.9.3+.

type Menu struct {
	// contains filtered or unexported fields
}

Menu is the splash / pause menu surfaced when the player presses Esc on the orbit (home) screen. Three actions: save, load, quit (autosave). Replaces the v0.6.3 inline "Quit and save? y/N" footer prompt with a centered modal that doubles as a "what can I do from here" entry point. v0.7.3.3+.

v0.7.4+ adds clickable controls. The list state shows three labelled buttons; clicking any transitions into a confirm sub-state with [Yes] / [No] buttons. Yes triggers the corresponding MenuAction; No returns to the list. The keyboard path keeps the legacy direct flow (s/l/q execute immediately) so muscle memory still works — the confirm gate is for the mouse path only.

func NewMenu added in v0.7.4

func NewMenu(th Theme) *Menu
func (m *Menu) HandleClick(col, row int) MenuAction

HandleClick maps a (col, row) click to a MenuAction. List-state clicks on Save / Load / Quit transition into the corresponding confirm sub-state and return MenuActionNone (the action only fires when the player confirms with Yes). Confirm-state Yes returns the stored action; No returns to the list.

func (m *Menu) HandleKey(s string) MenuAction

HandleKey maps a raw key string to a MenuAction. Lower- and upper-case both match. The keyboard path skips the click-only confirm gate when in the list state — typing "s" still saves immediately, matching v0.7.3.3 muscle memory. In a confirm state the keys narrow to y / n / enter / esc.

func (m *Menu) HitBackButton(col, row int) bool

HitBackButton reports whether a click at (col, row) lands on the title-row [Back] button. Kept as a public method for App.Update's mouse cascade — though HandleClick also handles [Back], having a dedicated check lets the dispatcher differentiate "left the menu" vs "clicked something inside it." v0.7.4+.

func (m *Menu) Render(width int) string

Render returns the menu screen for the current mode. width is the terminal width — used to right-align the [Back] button on row 0 the same way the orbit-screen title bar does.

func (m *Menu) Reset()

Reset returns the menu to its top-level list state. Called by the App when transitioning into screenMenu so the screen always opens in the action-list view, not whatever confirm state was last dismissed.

type MenuAction int

MenuAction enumerates the menu's outcomes. Returned by HandleKey and HandleClick so the caller (App.Update) can perform the actual save / load / quit dispatch — the screen stays decoupled from sim and save packages.

const (
	MenuActionNone   MenuAction = iota // unhandled key / click
	MenuActionCancel                   // esc / [Back] — return to orbit
	MenuActionSave
	MenuActionLoad
	MenuActionQuit
)

type Missions added in v0.7.4

type Missions struct {
	// contains filtered or unexported fields
}

Missions is the v0.7.4+ dedicated mission-list screen. Reachable from the orbit-screen title bar's `[Missions]` button (or the keyboard via app.go's MissionsKey). Lists every loaded mission with its current status; the orbit HUD's MISSION block was removed in v0.7.4 to give the right-hand pane back to flight state.

func NewMissions added in v0.7.4

func NewMissions(th Theme) *Missions

func (*Missions) HitBackButton added in v0.7.4

func (m *Missions) HitBackButton(col, row int) bool

HitBackButton reports whether a click at (col, row) lands on the title-row [Back] button. v0.7.4+.

func (*Missions) Render added in v0.7.4

func (m *Missions) Render(w *sim.World, width int) string

Render returns the mission list with status indicators and a progress summary. Empty-catalog worlds show a placeholder so the player isn't faced with a blank screen. width is the terminal width — used to right-align the [Back] button on row 0.

type NavballControlID int

NavballControlID identifies a clickable region in the navball panel. The zero value navballControlNone means "no hit".

const (
	NavballControlMode NavballControlID // cycle NavMode (orbit / surface / target)
	NavballControlSAS                   // toggle InstantSAS (MANUAL slew <-> AUTO instant)
	NavballControlPrograde
	NavballControlRetrograde
	NavballControlNormalPlus
	NavballControlNormalMinus
	NavballControlRadialOut
	NavballControlRadialIn
	NavballControlRCS         // toggle EngineMain <-> EngineRCS
	NavballControlTargetPlus  // hold toward target (BurnTarget)
	NavballControlTargetMinus // hold away from target (BurnAntiTarget)
)

type NodeClearAllMsg added in v0.8.6

type NodeClearAllMsg struct{}

NodeClearAllMsg is emitted when the player presses c / C (or the ctrl+k back-compat alias) in the maneuver form. The app handles it by calling World.ClearNodes() and closing the screen. Replaces the v0.8.5-and-earlier `N` global keybinding for the wipe-all case. v0.8.6+; primary binding simplified to `c` in v0.10.1.

type NodeDeleteMsg added in v0.8.6

type NodeDeleteMsg struct {
	EditingIdx int
}

NodeDeleteMsg is emitted when the player presses ctrl+d in the maneuver form while editing a planted node. The app handles it by calling World.DeleteNode(EditingIdx) and closing the screen. Replaces the v0.8.5-and-earlier `N` global "clear all nodes" keybinding for the per-node case. v0.8.6+.

type OrbitView

type OrbitView struct {
	// contains filtered or unexported fields
}

OrbitView renders the system's bodies, orbit lines, and a right-side HUD. Phase 0 — no spacecraft yet; C16 will add a glyph + augmented HUD.

func NewOrbitView

func NewOrbitView(th Theme) *OrbitView

NewOrbitView constructs the screen with an initially-small canvas; a Resize call from the root model sizes it to the terminal.

func (*OrbitView) ComposeNavballOverlay added in v0.11.4

func (v *OrbitView) ComposeNavballOverlay(w *sim.World, canvasStr string, cCols, cRows int) string

ComposeNavballOverlay is the exported entry the LaunchView calls to drop the same bottom-right navball panel into its canvas (sub-scope 6 / v0.11.4+). recordControls is forced false — the launch screen doesn't take navball clicks; the orbit-screen path keeps its hit-box capture by going through the private helper directly.

func (*OrbitView) HitAt added in v0.6.4

func (v *OrbitView) HitAt(screenCol, screenRow int) widgets.CellTag

HitAt translates a screen-space mouse coordinate (col, row) into a CellTag from the orbit canvas. The orbit screen renders title (1 row) + canvasPanel (rounded border = 1 row top, 1 col left) before the canvas content, so we offset (col-1, row-2). Returns a zero-value CellTag when the click lands outside the canvas content area, which the caller treats as "no hit." v0.6.4+.

func (*OrbitView) HitHudNode added in v0.8.2

func (v *OrbitView) HitHudNode(col, row int) (int, int, bool)

HitHudNode resolves a screen-space click against the HUD's NODES block. Returns (craftIdx, nodeIdx, true) when the row matches a recorded NODES entry and the column lands in the HUD region; (-1, -1, false) otherwise. v0.8.2.x:

  • row matching uses the post-render scan in scanHudNodeRows.
  • hudScrollOffset compensates for terminal scrolling when the rendered HUD overflows the visible area.

func (*OrbitView) HitMenuButton added in v0.7.4

func (v *OrbitView) HitMenuButton(col, row int) bool

HitMenuButton reports whether a click at (col, row) lands on the title bar's `[Menu]` button. Title bar lives on row 0 of the rendered orbit view. v0.7.4+.

func (*OrbitView) HitMissionsButton added in v0.7.4

func (v *OrbitView) HitMissionsButton(col, row int) bool

HitMissionsButton reports whether a click at (col, row) lands on the title bar's `[Missions]` button. v0.7.4+.

func (*OrbitView) HitNavballControl added in v0.9.6

func (v *OrbitView) HitNavballControl(col, row int) (NavballControlID, bool)

HitNavballControl maps a screen-space click to a navball-panel control, returning navballControlNone (ok=false) on a miss. Hit boxes are recomputed every Render in absolute screen coordinates. The app's mouse handler calls this; the dispatch from each control id into the NavMode cycle / SAS-hold intent is the follow-up wiring step (the panel layout intentionally reserves the room). v0.9.6-polish.

func (*OrbitView) IsCanvasClick added in v0.6.4

func (v *OrbitView) IsCanvasClick(col, row int) bool

IsCanvasClick reports whether a screen-space (col, row) lands inside the canvas content area (i.e. between the rounded-border edges). v0.6.4 mouse dispatch uses this to distinguish "click on orbit canvas" from "click on HUD" when no body / vessel / node hit lands.

func (*OrbitView) IsHudClick added in v0.6.4

func (v *OrbitView) IsHudClick(col int) bool

IsHudClick reports whether a screen-space col lands on the HUD panel (right of the canvas + its border).

func (*OrbitView) ProjectToOrbit added in v0.6.4

func (v *OrbitView) ProjectToOrbit(w *sim.World, screenCol, screenRow int) (time.Duration, bool)

ProjectToOrbit maps a screen-space click to the time-of-flight at which the active craft reaches the orbit point nearest the click. Used by the v0.6.4 empty-canvas mouse path to stage a new burn at "this point along my orbit."

Algorithm: sample 360 true-anomalies on the live craft orbit, project each to canvas pixels, take the smallest screen distance to the click pixel; convert that ν to a time-of-flight via orbital.TimeToTrueAnomaly. ok=false for hyperbolic / no-craft states or when no sample lands on-canvas.

func (*OrbitView) Render

func (v *OrbitView) Render(w *sim.World, selectedIdx int, totalCols, totalRows int) string

Render composes the frame: canvas on the left, HUD on the right. selectedIdx is the index of the cursor-selected body in system.Bodies.

func (*OrbitView) RenderHUDColumn added in v0.11.0

func (v *OrbitView) RenderHUDColumn(w *sim.World, selectedIdx, width int) string

RenderHUDColumn exposes the right-side HUD block contents (VESSEL / PROPELLANT / ATTITUDE / LAUNCH / NAVBALL / NODES / etc.) so sibling screens — currently v0.11.0+ LaunchView — can pair the same chrome with their own canvas rather than duplicating each block's formatter. Mutates the hit-test state the same way the in-screen render does; harmless for screens that don't route HUD clicks.

func (*OrbitView) Resize

func (v *OrbitView) Resize(totalCols, totalRows int)

Resize forwards terminal dimensions to the canvas. Left ~70% for canvas, right ~30% for HUD per plan §Screen details.

func (*OrbitView) ZoomIn

func (v *OrbitView) ZoomIn()

ZoomIn / ZoomOut are thin wrappers for App to call on +/-.

func (*OrbitView) ZoomOut

func (v *OrbitView) ZoomOut()

type Porkchop added in v0.3.3

type Porkchop struct {
	// contains filtered or unexported fields
}

Porkchop renders a launch-window Δv heatmap for a Hohmann transfer to the target body — grid of departure-day × time-of-flight with cells filled by an intensity character reflecting total Δv.

Simplistic ASCII render (no color) to stay portable across terminals without touching lipgloss color config. Darker/heavier glyph = lower total Δv ("cheaper" transfer). "·" marks infeasible / non-converged.

v0.10.5: a transfer-options sub-menu (toggled with `o`) holds the per-cell Lambert solve params (nRev, retrograde, short/long branch). Toggling re-runs the grid and rescales the TOF axis so multi-rev cells live in a sensible TOF range.

func NewPorkchop added in v0.3.3

func NewPorkchop(th Theme) *Porkchop

NewPorkchop constructs an empty screen. Call Load(world, targetIdx) before the first render to populate the grid.

func (*Porkchop) HandleKey added in v0.3.3

func (p *Porkchop) HandleKey(msg tea.KeyMsg) (tea.Cmd, bool)

HandleKey routes arrow keys within the grid. Returns done=true on Esc (cancel) or Enter (plant → app reads PendingPlant()) to signal the app should return to orbit view. Enter also sets the plant flag, checked by the app immediately after done=true.

With the transfer-options sub-menu open (`o`), keys instead drive the option toggles (n nRev / r retrograde / b branch) and esc/enter/o closes the menu — re-solving the grid on close.

func (*Porkchop) HitCell added in v0.6.4

func (p *Porkchop) HitCell(col, row int) (depIdx, tofIdx int, ok bool)

HitCell maps a screen-space (col, row) onto the porkchop grid's (depIdx, tofIdx). Title + blank line take rows 0 and 1; grid starts at row 2. Each row begins with "tof XXXd │" = 10 chars (gridLead) before the first cell. Returns ok=false when the click lands outside the grid's pixel rectangle. v0.6.4 mouse dispatch sets selDep / selTof from the result.

func (*Porkchop) Load added in v0.3.3

func (p *Porkchop) Load(w *sim.World, targetIdx int)

Load computes the porkchop grid for the given target body and caches it on the screen. Cheap to call once on open; not recomputed per frame since the grid doesn't change unless the craft's state or target shifts, and the player pauses on this screen to select a cell anyway. v0.10.5: resets transfer options to single-rev prograde short on each fresh Load (the sub-menu drives subsequent re-solves).

func (*Porkchop) PendingPlant added in v0.4.1

func (p *Porkchop) PendingPlant() (targetIdx int, depDay, tofDay float64, opts sim.TransferOptions, ok bool)

PendingPlant returns the selected (targetIdx, depDay, tofDay, opts) if the user pressed Enter on a feasible cell; ok=false if no plant is pending (Esc-close or infeasible cell). Consumes the pending flag — next call returns ok=false until the user plants again. v0.10.5 also returns the active TransferOptions so PlanTransferAt uses the same nRev/retrograde/branch the cell was scored against.

func (*Porkchop) Render added in v0.3.3

func (p *Porkchop) Render(w *sim.World, cols, rows int) string

Render draws the grid + axes + selection readout.

func (*Porkchop) SetSelection added in v0.6.4

func (p *Porkchop) SetSelection(depIdx, tofIdx int)

SetSelection moves the cursor to (depIdx, tofIdx) — used by the v0.6.4 mouse dispatch on click. Bounds-checked; out-of-range indices are ignored.

type SpawnAction added in v0.8.2

type SpawnAction int

SpawnAction enumerates the form's outcomes.

const (
	SpawnActionNone    SpawnAction = iota
	SpawnActionCancel              // esc
	SpawnActionConfirm             // enter — caller reads accessors
)

type SpawnCraft added in v0.8.2

type SpawnCraft struct {
	// contains filtered or unexported fields
}

SpawnCraft is the modal form opened by `n` on the orbit screen. v0.8.2+: four fields — craft type, parent body, altitude, and direction (prograde / retrograde). v0.8.3+ added a POSITION toggle (orbit / alongside). v0.9.2+ extended POSITION with a third option (launchpad) and the ALTITUDE field doubles as a LATITUDE picker when launchpad is selected. Tab cycles field focus; ←/→ edit the focused field; Enter spawns; Esc cancels.

func NewSpawnCraft added in v0.8.2

func NewSpawnCraft(th Theme) *SpawnCraft

NewSpawnCraft constructs the screen.

func (*SpawnCraft) CustomStackEmpty added in v0.10.1

func (s *SpawnCraft) CustomStackEmpty() bool

CustomStackEmpty reports Custom-selected-but-no-stages — the one confirm state the caller must reject (an empty stack is not a spawnable craft). v0.10.1+.

func (*SpawnCraft) HandleKey added in v0.8.2

func (s *SpawnCraft) HandleKey(key string) SpawnAction

HandleKey maps a raw key string to a SpawnAction. Tab cycles fields; ←/→ edit the focused field; Enter commits; Esc cancels.

func (*SpawnCraft) IsCustomSelected added in v0.10.1

func (s *SpawnCraft) IsCustomSelected() bool

IsCustomSelected reports whether the cursor is on the synthetic "Custom…" CRAFT TYPE entry (the last row). v0.10.1+.

func (*SpawnCraft) Render added in v0.8.2

func (s *SpawnCraft) Render(width int) string

Render returns the modal form. Width is the terminal width.

func (*SpawnCraft) Reset added in v0.8.2

func (s *SpawnCraft) Reset(systemBodies []bodies.CelestialBody, defaultParentID string)

Reset prepares the form for a fresh open. systemBodies is the list of bodies in the active system; defaultParentID is the body the parent-field cursor lands on initially (typically the active craft's current primary). v0.8.2+: replaces the v0.8.2-pre no-arg Reset.

func (*SpawnCraft) SelectedAlongside added in v0.8.3

func (s *SpawnCraft) SelectedAlongside() bool

SelectedAlongside reports whether the player picked the "alongside active craft" position. v0.8.3+.

func (*SpawnCraft) SelectedAltitudeM added in v0.8.2

func (s *SpawnCraft) SelectedAltitudeM() float64

SelectedAltitudeM returns the chosen altitude above the parent's mean radius (m).

func (*SpawnCraft) SelectedCustomStages added in v0.10.1

func (s *SpawnCraft) SelectedCustomStages() []spacecraft.Stage

SelectedCustomStages returns a copy of the player-assembled stack (bottom-first), or nil when Custom is not selected. The spawn path treats a nil/empty result as "no custom craft". v0.10.1+.

func (*SpawnCraft) SelectedLatitudeDeg added in v0.9.2

func (s *SpawnCraft) SelectedLatitudeDeg() float64

SelectedLatitudeDeg returns the chosen surface latitude (degrees north) when SelectedLaunchpad is true. Defaults to KSC LC-39A (28.6083°N) when the cursor is out of range. v0.9.2+.

func (*SpawnCraft) SelectedLaunchpad added in v0.9.2

func (s *SpawnCraft) SelectedLaunchpad() bool

SelectedLaunchpad reports whether the player picked the surface-launchpad position. v0.9.2+.

func (*SpawnCraft) SelectedLoadoutID added in v0.8.2

func (s *SpawnCraft) SelectedLoadoutID() string

SelectedLoadoutID returns the loadout ID for the current cursor, or "" when the synthetic Custom entry is selected (the caller then reads SelectedCustomStages instead). v0.10.1+.

func (*SpawnCraft) SelectedLongitudeEastDeg added in v0.9.2

func (s *SpawnCraft) SelectedLongitudeEastDeg() float64

SelectedLongitudeEastDeg returns the chosen surface longitude offset (degrees east of pseudo-Greenwich). Defaults to KSC (-80.604°E) when the cursor is out of range. v0.9.2+.

func (*SpawnCraft) SelectedParentID added in v0.8.2

func (s *SpawnCraft) SelectedParentID() string

SelectedParentID returns the body ID the cursor is on, or empty if the body list is unset (caller falls back to the active craft's primary).

func (*SpawnCraft) SelectedRetrograde added in v0.8.2

func (s *SpawnCraft) SelectedRetrograde() bool

SelectedRetrograde reports whether the player picked retrograde.

type SpritePixel added in v0.11.3

type SpritePixel struct {
	OffsetWorld orbital.Vec3
	Color       lipgloss.Color
}

SpritePixel is one braille sub-pixel of a composed launch sprite — a world-frame offset from the vessel anchor (`c.State.R + bodyPos`) plus the color the render path should PlotColored at that position. No glyph: braille dots are direction-agnostic, so a tilted rocket renders smoothly at any pitch without per-glyph rotation (v0.11.3 playtest pivot from ASCII glyphs to braille pixels — see docs/v0.11-plan.md "Resolved at slice-open").

func ComposeFlame added in v0.11.3

func ComposeFlame(stages []spacecraft.Stage, cmdWorld orbital.Vec3, basis widgets.Basis, scaleMPerPx float64, throttle float64, frameIdx int) []SpritePixel

ComposeFlame builds exhaust-flame pixels extending below Stages[0]'s base along the -cmdWorld direction. Same `spriteWidthPx` width as the rocket sprite. Length-binned by throttle into 3 bins:

  • throttle ≤ 0 or no stages: returns nil.
  • 0 < throttle ≤ 1/3: 4 sub-pixel rows (1 cell tall).
  • 1/3 < throttle ≤ 2/3: 8 sub-pixel rows.
  • 2/3 < throttle: 12 sub-pixel rows.

frameIdx selects one of two pulse offsets — frame B shifts the flame down by 1 px so the dot pattern within each cell visibly changes between frames at the ~100 ms wall-clock cadence. Colour: amber `render.ColorWarning`.

func ComposeLaunchSprite added in v0.11.3

func ComposeLaunchSprite(stages []spacecraft.Stage, cmdWorld orbital.Vec3, basis widgets.Basis, scaleMPerPx float64) []SpritePixel

ComposeLaunchSprite builds the composed-from-stages rocket sprite as a list of braille sub-pixels. Stages stack bottom-to-top from Stages[0] along the projection of cmdWorld into the chase-cam basis (so a gravity-turned rocket leans visibly); each stage contributes a `spriteWidthPx × Stage.LaunchSpriteRowsPx` filled rectangle of pixels in the stage's catalog color. Returns nil when no stage has a non-zero LaunchSpriteRowsPx — caller falls back to the vessel's single Glyph render.

Each "pixel" is one braille sub-cell dot (`scaleMPerPx` metres across); the canvas's PlotColored accumulates dots per cell and renders the resulting braille char.

type Theme

type Theme struct {
	Primary, Warning, Alert, Dim, HUDBox, Footer, Title lipgloss.Style
}

Theme is the subset of styles OrbitView needs. Passed in from tui.App.

Jump to

Keyboard shortcuts

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