# Linear telescope scripts

Open **Linear Script** between Sequences and Surveys. Create an ordered list of
commands, name it, and Save. Load reopens a saved script; Save as copy creates a
separate script. Use Run to save and queue the current version. It starts after
the active FIFO/calendar operation returns, before another automatic job starts.
The ordinary direct-control permissions apply to Run and Resume.
Manual execution also uses OBS's existing `_command_allowed_for_current_scope`
and `_command_allowed_during_outreach_mode` admission checks. A changed command
scope or outreach mode pauses the line rather than bypassing those controls.
An authorized Real Time reservation continues to permit direct scripts inside
that window; it is not treated as an automatic calendar run to wait behind.

## Manual mode

**Run script in manual mode** is visible only to global admins, site owners and telops,
and defaults on for them. An explicit off is saved with the script. Other users
have this option hidden and off, including when loading an owner's script.
Run sends the checkbox value explicitly to Hub. Hub checks the current actor's
roles and includes the authorized boolean in the OBS run payload. A missing
option does not enable manual mode in Hub or OBS.
When enabled, OBS enters manual mode after the current operation finishes and
the script passes the existing admission checks. It retains that mode between
lines, then restores the preceding mode on completion, pause, STOP or loss of
Hub control. An existing device fault/global stop that forces manual mode is
preserved. With the option off, the script leaves the current telescope mode alone.
Manual-mode exposures do not require a successful IRF90 autofocus sky PA
calibration/check. Automatic science and focus exposures retain that requirement.

## Command permissions

Dark exposures (single and video), Set rotator field angle, Start rotator and Stop rotator are
offered only to site owners, telops, site known users and global admins. Other
commands remain available under the existing direct-control permissions. Hub
enforces these roles on save, Run and Resume; hiding a control is not the
authorization boundary. Calendar runs use the booking creator's roles, and
pause with an explicit error before a command that creator cannot execute.

### Admin blocks

Global admins can use **Add admin block** to insert one of the existing calendar
Admin Block actions. Hub builds the executable payload from that action; scripts
cannot supply an arbitrary raw device command. The block runs in line order on
OBS's normal command thread, and action failures pause the script. Restacking
retains its existing behavior of submitting work to the calibration worker.
Owners, telops and known users do not receive the script Admin Block option.

## Pointing and focus

**Move to RA and Dec** includes **Centering exposure**, default on. It waits for
the slew, runs the existing centering routine, and pauses if centering fails.
Turn it off for a plain coordinate slew. **Move focuser** accepts a direct
position or signed relative offset in device steps and waits for verified completion;
relative offsets are added to the current position in steps. Small
requested moves are sent instead of being skipped by the direct-control deadband.

## Exposure commands

Light, dark and **Take flat exposures** lines include filter, exposure count and
duration. Flats use individual illuminated frames and retain flat-frame FITS
metadata. Exposure menus use the usual single-frame or video snap-to-grid values;
**Custom** accepts any positive finite floating-point duration without snapping
or imposing the menu's upper bound. Saved custom values reopen as Custom.
For SmartStack, a custom total uses a final partial frame when needed.

### Raw files and local calibration copies

Each light/dark line also offers **Add to local calibrations**, default off. Its
tooltip discourages this because the system manages its own calibrations, and
explains that FITS files are produced as normal. Enabling it writes an additional
copy of the raw pixels into the selected camera's localcalibrations store: darks
use the existing dark folder and lights use a separate `lights` folder. Native
video copies retain the SER/frame spool and capture sidecars. The extra copy is
independent of normal raw FITS saving and does not relabel lights as darks.

**Save raws** is a script-wide option available to all roles, default on. It
overrides OBS's normal raw-saving setting for captures made by the script,
including nested captures; an explicit off disables the script's raw FITS
saving. The observatory configuration is unchanged. On also retains original
native video frame spools after processing. Saved scripts and calendar/run
snapshots retain these options; older scripts default to on with local copies off.

## Camera cooling

Light and dark lines retain their cooling tooltip. Before any light/dark
acquisition mode, a cooled camera is explicitly set to its
configured nighttime target and capture waits until its measured temperature is
within the camera's existing temperature tolerance. STOP cancels this wait.
Missing temperature telemetry errors before capture.
The script commands the nighttime target and cooling directly; ordinary OBS
temperature scheduling does not send competing commands for that camera while
the line owns it. The script reports the measured temperature while it cools.
A rejected temperature or cooling-enable command pauses the line with its actual
driver error, including PICam communication errors, rather than waiting forever
for an unapplied request. An already cold sensor does not skip cooling commands.
OBS logs line starts, failures, completions and cooling progress, so a resumed
line's activity is visible in its foreground console and live logs.
The selected camera stays
on its night target throughout that exposure line; normal temperature policy
resumes afterwards. Existing overheat recovery is retained, other cameras keep
their normal policy, and uncooled camera profiles continue without a temperature
wait because they have no regulated target. Dark video retains a selected filter
when a shutter, darkslide or dark selector can close the optical path; if only a
physical dark filter can do that, a conflicting explicit filter is an error.

Cooling emits a heartbeat every 30 seconds with the camera name, measured
temperature and nighttime target, including when the temperature has not changed.

## Rotator control

**Start rotator** explicitly enables IRF90 derotation and ongoing field correction
to the selected field angle, including in manual mode and before sky PA
calibration. It retains the mount's current tracking state; use **Set tracking**
to change that separately. **Stop rotator** halts movement, disables IRF90
derotation and suspends field correction until another Start. Existing mount
slew/Home handoffs, device faults, exposure holds and global Stop still apply.
The Start command reports an error on rotators without explicit derotation
support; it does not silently substitute a field-angle move.

## Camera warming and power off

**Warm ramp to warm ready temperature** ramps the selected cooled camera over
one hour to its configured day-warm temperature (night target plus day-warm
degrees). **Warm ramp to camera off** warms all cameras on the existing shared
camera power supply to ambient over one hour, confirms that they have warmed,
then switches that supply off. Cameras already warm enough need no warming wait.
Actual camera power control is required for the off command; unavailable or
failed power control pauses the line rather than substituting connected standby.
Both commands report warming progress every 30 seconds. STOP cancels the wait
and retains the last commanded temperature without switching power off.

Warm/off state persists after the line and script finish. A subsequent exposure
or operator temperature command releases it; after power off, the next exposure
restores the shared camera supply and reconnects before acquisition. Setting an
operator temperature override or releasing that override also releases the
script's temperature hold. Normal policy continues for cameras outside the
shared off operation. These temporary holds do not change saved configuration
and end on OBS restart.

## Target names

**Set target name** sets the name inherited by later exposures. Entering a name
on an exposure also sets that name; later blank exposures inherit it. Named
moving-target slews also establish their target name.
If no name exists, the editor warns and the run records a warning without
blocking acquisition. Names are labels and do not change moving-target ephemerides.

## Calendar scheduling

Select **Linear Script** as the calendar event type and choose a saved script to
reserve a run. Existing calendar access, conflict, time-window and billing rules
apply. Each reservation contains a snapshot, so later library edits do not change
already booked or running instructions. Changing the selected script in a future
booking takes a new snapshot.

## Progress, errors and Resume

The run list shows queued/running/paused/stopping/stopped/completed status and the
line number. Returning to the page automatically loads the active run's saved
instructions into the editor, including a calendar run or a run started in
another window. The current running row has an accent background and border,
and its legend shows Running; a paused row uses a warning highlight and shows
the actual error. The highlight follows the durable line index as it advances.
Navigating away or closing the browser stops only status reads: OBS continues
the script independently. Reopening restores its current state without starting
a duplicate run or automatically resuming a genuine error. Choosing New/Load or
editing makes a separate draft; View active script returns to the run snapshot.
Select any run to inspect every line and its command history. A device
refusal, unavailable device, failed ephemeris lookup or exposure error pauses at
that line. Resume explicitly retries the line; a multi-exposure line may repeat
exposures that completed before its failure. No later line runs automatically
after an error. Ordinary direct commands can be used to correct a problem while
paused; automatic observing remains held until Resume or STOP.

## STOP

STOP removes a queued run immediately. During an ordinary command it waits for
that command to finish. During light/dark/flat acquisition or a rotator field-angle
move, it sends the relevant device stop and prevents the next line. It does not
issue a mount stop while a Home, Park or slew is finishing. The existing global
observatory stop still applies. A scheduled script pauses when its reservation
ends or is removed, using the existing exposure calendar checks as well.

## Coordinates and capture modes

RA is in hours; Dec, altitude, azimuth and field angle are in degrees. Minor
planets and comets resolve orbital elements by name at execution, or use elements
explicitly loaded in the editor. Positions are
resolved at execution time. Major planets use the existing Hub ephemeris service.
Exposure modes use the existing single, video, planet and paired speckle capture
paths, including their hardware capabilities and requirements. Speckle settings
are entered with the existing speckle editor.
An explicit Single exposure retains its requested duration, including on the
Moon. If the existing daytime exposure limit would shorten it, the command
errors instead of silently taking a shorter exposure.

Dark video must be able to close its optical path: an existing darkslide,
external shutter, dark selector path or physical `dk` filter. This is a technical
requirement for recording dark video on the supported native live-video cameras,
whose live-capture APIs otherwise record illuminated frames. If the camera
cannot do this, the line errors before capturing. Dark videos retain dark frame
metadata and omit light-frame calibration/plate-solve bookends. A subsequent
light video opens the shutter/darkslide again. Missing named filters are errors;
the legacy wheel's automatic substitute selection is not used by scripts.

## Source and runtime

Hub owns `linear_scripts.py`, the editor and the durable run journal. Its new
PostgreSQL auxiliary tables are installed by runtime schema version 22 through
the existing startup migration. SQLite is used only by the established test
harness. Programs and acknowledgements are small JSON records; no science files
are scanned or hashed by the script feature.

Calendar requests use the existing Arbiter Sequence Session transport with a
`kind: linear_script` snapshot and `linear-script:<id>` sequence reference. Hub
and OBS recognize that marker before normal sequence conversion. Arbiter retains
the reservation/snapshot through its existing API; no Arbiter code or schema
change is needed.

The neighbouring OBS checkout owns `linear_script_runner.py` and
`linear_script_capture.py`. The existing
foreground OBS control loop runs one script line at a time and prevents a new
automatic job or queued direct command from interleaving with a running script.
Only the control/status poll runs in another thread, so STOP can reach a blocking
exposure. Its API uses the existing Hub control WebSocket; both checkouts add the
script service paths to `hub_control_protocol.py`.

Each run has an OBS session ID. After an OBS restart, an in-flight line is paused
for inspection rather than replayed. Completion acknowledgements are idempotent:
a lost response can be retried without repeating hardware activity. A transport
failure during a run prevents the next line and records a pause when Hub is
reachable again.

Deploy/update both existing checkouts and run the normal Hub frontend build and
foreground console launchers. No Python package installation, service
registration, relocated web assets or new background application is required.
Local contract tests do not establish live telescope execution or hardware stop
latency; those still require an operator-controlled commissioning run.
