import { Engine, Observer, HouseSystem } from "./chart.js"; import type { SyntheticRender } from "./synthetic.js"; export type SkyProjection = "rectilinear" | "fisheye"; /** A resolved lens: field of view and projection actually used. */ export interface SkyLens { name: string; focalLengthMm: number; sensorWidthMm: number; projection: SkyProjection; hfovDeg: number; vfovDeg: number; } export declare const LENS_NAMES: string[]; export type LensSpec = string | { focalLengthMm: number; sensorWidthMm?: number; projection?: SkyProjection; } | { hfovDeg: number; projection?: SkyProjection; }; export declare function resolveLens(spec: LensSpec, width: number, height: number): SkyLens; /** Where a direction lands on the image plane (see {@link skyPlacer}). */ export interface SkyPlacement { /** Apparent altitude after refraction (or the true altitude when off). */ altApp: number; /** Pixel position; NaN when the direction is behind a rectilinear camera. */ x: number; y: number; inFrame: boolean; /** Great-circle distance from the aim, degrees. */ deltaDeg: number; /** Which frame edge the direction is beyond (or behind the camera). */ side: "left" | "right" | "above" | "below" | "behind"; } /** * The projection at SkyView's heart as a standalone, pinnable function: build * the camera basis for an aim, and return the placer mapping an (azimuth, * true-altitude) direction to the image plane under the lens's projection * (rectilinear or fisheye), with optional atmospheric refraction. `skyView` * composes this internally; the golden suite pins it directly against the * Python reference (astroengine.skyview.place). */ export declare function skyPlacer(aimAzDeg: number, aimAltDeg: number, lens: SkyLens, width: number, height: number, opts?: { refraction?: boolean; pressure?: number; tempC?: number; }): (azDeg: number, altTrueDeg: number, refractThis?: boolean) => SkyPlacement; export type TwilightStage = "day" | "civil" | "nautical" | "astronomical" | "night"; export declare function twilightStage(sunAltDeg: number): TwilightStage; /** Effective naked-eye limiting magnitude: the more restrictive of the * twilight-brightness ceiling and the site's dark-sky limit, then reduced when * a bright Moon is up. With no Bortle class, the dark-site limit defaults to * 6.0 (suburban), matching the original behavior. */ export declare function limitingMag(stage: TwilightStage, moonAltDeg: number | null, moonIllum: number | null, bortle: number | undefined): number; /** Sun and Moon state feeding {@link skyBrightness}. */ export interface SkyBrightnessCtx { sunAltDeg: number; sunAzDeg: number; moonAltDeg: number; moonAzDeg: number; /** Illuminated fraction, 0..1. */ moonIllum: number; /** Bortle dark-sky class 1-9; omitted means class 6 (bright suburban). */ bortle?: number; } /** * Approximate V-band sky surface brightness (mag/arcsec^2) of the sky at a * direction: a render cue for the sky gradient, not precision photometry. * Composes, in linear flux space, (a) the site's moonless zenith sky by * Bortle class ({@link BORTLE_ZENITH_MAG}), brightened toward the horizon by * the airglow term ({@link AIRGLOW_HORIZON_MAG}, scaled by the Kasten & * Young airmass excess); (b) twilight from the Sun's altitude * ({@link TWILIGHT_RAMP}, treated as direction-independent -- the azimuthal * afterglow gradient stays a prose cue via `brightestAzimuth`); and (c) a * Krisciunas & Schaefer-inspired moonlight term (PASP 103, 1033, 1991): * scattered moonlight falling off ~0.04 mag per degree of angular distance * from the Moon (their 10^(-rho/40) scattering wing, softened toward the * measured large-angle brightening), scaling linearly with illuminated * fraction and dimmed by the extra airmass along the Moon's own path. The * anchors reproduce the model's full-Moon predictions at a dark site: sky * ~17.5 near the Moon, ~1.5 mag of brightening 90 deg away. Typical checks: * Bortle 1 night zenith 21.9, its horizon 21.5, clear daytime ~4.0. */ export declare function skyBrightness(altDeg: number, azDeg: number, ctx: SkyBrightnessCtx): number; /** One skyline sample: the obstruction altitude at an azimuth. */ export interface HorizonPoint { azDeg: number; altDeg: number; } /** * Skyline altitude at an azimuth from an observer obstruction profile: * piecewise-linear interpolation on the circle. The profile need not be * sorted; azimuths are normalized to [0, 360) and the interpolation wraps * across 360/0. An empty profile is the astronomical horizon (0 deg). */ export declare function horizonAltAt(profile: readonly HorizonPoint[], azDeg: number): number; export interface SkyAim { /** Center azimuth: degrees from true north (east positive), or a compass * point (`"W"`, `"WNW"`). */ azimuth: number | string; /** Center altitude in degrees: 0 looks at the horizon, positive tilts up. */ altitude: number; } export interface SkyViewSpec { observer: Observer; aim: SkyAim; lens: LensSpec; image: { width: number; height: number; }; } export interface SkyViewOptions { /** Atmospheric pressure (hPa) and temperature (C) for refraction. */ pressure?: number; tempC?: number; /** Lift apparent altitudes by refraction. Defaults to true. */ refraction?: boolean; /** Bortle dark-sky class, 1 (pristine) to 9 (inner city). Sets the night * naked-eye limit and drives the background star-field density and Milky Way * visibility. Omit for the legacy suburban default (limit 6.0). */ bortle?: number; /** Include bright catalog stars. Defaults to true when a catalog is loaded. */ includeStars?: boolean; /** Brightest-magnitude cutoff for stars (smaller is brighter). Default 2.5. */ maxStarMag?: number; /** Cap on the number of stars returned. Default 40 (named) or 4000 (deep). */ maxStars?: number; /** Pin the complete deep star field (needs the deep pack loaded). Defaults to * on when the sky is dark and no bright Moon is up. */ deepField?: boolean; /** Reference-frame overlays to project onto the sky (annotations, not part of * a photoreal render). */ overlays?: SkyViewOverlaysRequest; /** Observer obstruction profile: the skyline altitude per azimuth * (buildings, trees, ridgelines). Piecewise-linear on the circle via * {@link horizonAltAt}; input may be unsorted. A body above the * astronomical horizon but below the skyline at its azimuth is excluded * from `bodies` and recorded in `occluded`; stars below the skyline are * dropped silently. */ horizonProfile?: Array<{ azDeg: number; altDeg: number; }>; /** Prompt template: a {@link PROMPT_STYLES} key. Only the prompt and * render-plan prose change with the style; every computed number is * identical. Defaults to "default" (the original wording). */ promptStyle?: keyof typeof PROMPT_STYLES; /** Bodies to place. Defaults to Sun, Moon, and the naked-eye planets. Any * string id works for runtime bodies registered via {@link Engine.registerSource}. */ bodies?: readonly string[]; /** Per-body appearance overrides (size, magnitude, colour). Merged with * {@link Engine.renderFor} for registered bodies; opts win on conflict. */ render?: Record; } export interface SkyBody { id: string; name: string; azimuthDeg: number; altitudeDeg: number; x: number; y: number; inFrame: boolean; sizePx: number; angularDiameterDeg: number; magnitude: number | null; /** Atmospheric extinction at this altitude, magnitudes (Kasten & Young * air mass x 0.2 mag/airmass) -- the computed dimming a render pipeline * should apply near the horizon, instead of inventing its own. Null for * bodies without photometry. */ extinctionMag: number | null; /** Bright enough to see at this sky brightness. */ nakedEye: boolean; /** How prominent to render the body, derived from its magnitude (a prompt * cue, not a physical size). See {@link brightnessDescriptor}. */ brightnessHint?: string; /** Moon only: illuminated fraction, phase name, and bright-limb orientation. */ illum?: number; phaseName?: string; brightLimbAngleDeg?: number; brightLimbClock?: string; /** Authored colour hint for synthetic or overridden bodies. */ color?: string; note?: string; } export interface SkyOffFrameBody { id: string; name: string; side: "left" | "right" | "above" | "below" | "behind"; /** Angular distance from the frame center, degrees. */ deltaDeg: number; azimuthDeg: number; altitudeDeg: number; magnitude: number | null; } export interface SkySummary { twilight: TwilightStage; sunAltitudeDeg: number; sunAzimuthDeg: number; limitingMag: number; moonAltitudeDeg: number | null; moonIllum: number | null; /** Azimuth of the brightest part of the sky (afterglow, then Moon), or null. */ brightestAzimuthDeg: number | null; /** Approximate V-band sky surface brightness at the zenith and at the * horizon under the aim azimuth, mag/arcsec^2 (smaller is brighter; from * {@link skyBrightness}, a render cue rather than precision photometry). */ zenithBrightness: number; horizonBrightness: number; /** Pixel row of the true horizon, or null when it is behind the camera. */ horizonY: number | null; } /** A body hidden by the observer's obstruction profile: above the * astronomical horizon, below the skyline at its azimuth. */ export interface SkyOccludedBody { id: string; name: string; azimuthDeg: number; altitudeDeg: number; /** The skyline altitude at the body's azimuth. */ skylineAltDeg: number; } /** The Milky Way band's appearance in the frame. Visible only in a dark sky * (astronomical twilight or night) without a bright Moon or heavy light * pollution. The band is the galactic equator; its bright bulge is the * galactic center in Sagittarius. */ export interface MilkyWay { /** Dark enough to see, and at least partly above the horizon. */ visible: boolean; /** The band crosses the frame (implies `visible`). */ inFrame: boolean; /** Pixel where the band enters the frame, and where it exits. */ entry: { x: number; y: number; } | null; exit: { x: number; y: number; } | null; /** The galactic center (Sagittarius), the brightest part, when above the * horizon: its pixel, whether it is in frame, its altitude, and which way it * lies relative to the frame. */ galacticCenter: { x: number; y: number; inFrame: boolean; altitudeDeg: number; side: string; } | null; /** Plain-language state, e.g. "crosses the frame, bright center in view" or * "washed out by a bright Moon". */ note: string; } /** The visible celestial pole: the fixed point the whole sky rotates about as * time passes (north pole for northern observers, south for southern). Its * altitude equals the observer's |latitude|. The rotation center for star * trails and for reprojecting an animation frame to frame. */ export interface CelestialPole { which: "north" | "south"; altitudeDeg: number; /** Pixel of the pole, or null when it is behind the camera. */ x: number | null; y: number | null; inFrame: boolean; } /** What the star field in `bodies` represents. A `deep` field is the complete * naked-eye sky to `limitingMag`, pinned at exact pixels (animation-grade); a * `named` field is the bright catalog, to be fleshed out by the model. */ export interface StarfieldSummary { source: "deep" | "named" | "none"; /** Stars placed in the frame. */ count: number; /** The field is complete to `limitingMag` (deep pack); do not add stars. */ complete: boolean; limitingMag: number; } /** Which reference frames to project. The ecliptic, the zodiac signs (its 30 * degree divisions), the house cusps and angles, and the constellation figure * lines. These are annotations: real reference frames drawn over the sky, not * things a camera sees. */ export interface SkyViewOverlaysRequest { ecliptic?: boolean; signs?: boolean; houses?: boolean; constellations?: boolean; /** House system for the cusps. Defaults to Placidus. */ houseSystem?: HouseSystem; } /** A labeled point projected into the frame (sign, house, constellation name). */ export interface OverlayMark { text: string; x: number; y: number; /** Ecliptic longitude (deg), for signs and house cusps. */ lon?: number; } /** An in-frame polyline (the ecliptic, a constellation figure stroke). */ export interface OverlayLine { label?: string; points: Array<{ x: number; y: number; }>; } /** Projected reference-frame overlays. Each is null when not requested; arrays * hold only the in-frame parts. Pixels match the bodies' coordinate system. */ export interface SkyViewOverlays { ecliptic: OverlayLine[] | null; signs: OverlayMark[] | null; houses: OverlayMark[] | null; constellations: { lines: OverlayLine[]; labels: OverlayMark[]; } | null; } /** One layer of the hybrid render: what Caelus computes and the pipeline * composites locally over the image-model background plate. */ export interface RenderLayer { kind: "bodies" | "stars" | "milkyWay" | "overlays"; /** The layer has content in this frame. */ present: boolean; /** Items in the layer (bodies, stars, figure strokes...). */ count: number; /** How to composite this layer locally, over the plate. */ composite: string; } /** * A machine-readable render contract for a hybrid pipeline: ask an image model * for a body-free background plate, then composite the computed layers (bodies, * stars, the Milky Way, overlays) locally at their exact pixels. The image model * supplies atmosphere; Caelus supplies the physically correct objects. This is * the structured complement to `prompt`, which is for humans and image models. */ export interface RenderPlan { /** The body-free sky/atmosphere plate to generate with an image model. */ background: { prompt: string; width: number; height: number; /** Hard constraints on the plate (no bodies, horizon row, even sky). */ constraints: string[]; }; /** Layers to composite locally; do not ask the model to draw these. */ layers: RenderLayer[]; /** How to drive an animation. `static` for a single frame. */ animation: { strategy: "static" | "sequence-composite"; /** Sidereal rotation of the sky about the pole, degrees/hour. */ rotationDegPerHour: number; pole: CelestialPole; notes: string; }; /** Post-processing and grading notes for the composite. */ postprocess: string[]; } export interface SkyViewResult { instant: { jdUt: number; utc: string; }; observer: Observer; aim: { azimuthDeg: number; altitudeDeg: number; compass: string; }; lens: SkyLens; image: { width: number; height: number; }; sky: SkySummary; bodies: SkyBody[]; offFrame: SkyOffFrameBody[]; /** Bodies hidden by the `horizonProfile` skyline (empty without one). */ occluded: SkyOccludedBody[]; milkyWay: MilkyWay; pole: CelestialPole; starfield: StarfieldSummary; overlays: SkyViewOverlays | null; renderPlan: RenderPlan; directives: string[]; prompt: string; } /** One prompt template: the prose framing around the computed facts. */ export interface PromptStyle { /** First line of the serialized prompt. */ header: string; /** Extra style directive appended to the SCENE block ("" for none). */ sceneClause: string; /** First line of the render plan's body-free background-plate prompt. */ plateHeader: string; } /** Per-image-model prompt templates. A style changes only the prompt and * render-plan prose; every computed number (pixels, sizes, magnitudes, * brightness) is identical across styles. `default` is the original * wording, byte-identical to the pre-template output; `photoreal` speaks * camera and exposure vocabulary for photographic models; `illustration` * asks for a flat-colour scene while keeping the placements binding. */ export declare const PROMPT_STYLES: { default: { header: string; sceneClause: string; plateHeader: string; }; photoreal: { header: string; sceneClause: string; plateHeader: string; }; illustration: { header: string; sceneClause: string; plateHeader: string; }; }; /** * Project the visible sky into an image frame for a place, instant, aim, and * lens. Returns each in-frame body's pixel position, apparent size, brightness, * and (for the Moon) phase orientation, a sky-state summary, the bright bodies * just outside the frame, and a serialized prompt. * * Caelus computes geometry and photometry only. It does not render an image; * the `prompt` and `directives` hand color and atmosphere to an image model. * * @param engine The engine used to evaluate positions. * @param jdUt The instant, Julian Day (UT). For "at sunset", resolve it first * with `riseSet(engine, "sun", jdStart, lat, lonEast, "set")`. * @param view Observer, aim (azimuth and altitude), lens, and image size. * @param opts Refraction inputs, star selection, and the body set. * @returns A {@link SkyViewResult}. * @example * ```ts * const set = riseSet(engine, "sun", julianDay(2026, 6, 21), 47.6, -122.3, "set")!; * const view = skyView(engine, set, { * observer: { lat: 47.6, lonEast: -122.3, altM: 9 }, * aim: { azimuth: "W", altitude: 5 }, * lens: "normal", * image: { width: 1024, height: 683 }, * }); * view.bodies.find((b) => b.id === "moon")?.brightLimbClock; * ``` */ export declare function skyView(engine: Engine, jdUt: number, view: SkyViewSpec, opts?: SkyViewOptions): SkyViewResult; export interface SkyViewSequenceSpec { /** First frame instant, Julian Day (UT). */ startJdUt: number; /** Number of frames (>= 1). */ frames: number; /** Minutes between frames. Provide this or `endJdUt`. */ stepMinutes?: number; /** Last frame instant (UT); the step is `(end - start) / (frames - 1)`. * Takes precedence over `stepMinutes` when both are given. */ endJdUt?: number; } export interface SkyViewSequence { /** One full {@link SkyViewResult} per frame, in time order. */ frames: SkyViewResult[]; count: number; startJdUt: number; endJdUt: number; stepMinutes: number; durationMinutes: number; /** Sidereal rotation of the sky about the celestial pole: 15.041 deg/hour. */ rotationDegPerHour: number; /** Sky rotation between consecutive frames, degrees about the pole. */ rotationDegPerStep: number; } /** * A time sequence of {@link skyView} frames for the same place, aim, and lens: * the keyframes for an accurate night-sky animation. Each frame is a complete, * physically exact spec; across frames the sky rotates about the pole, the Moon * drifts and changes phase, twilight evolves, and the Milky Way wheels. The * geometry is continuous, so the frames are temporally coherent; supplying them * as control images (or reprojecting one rendered plate by the per-frame * rotation) is how to keep the rendered output coherent too. * * @param engine The engine used to evaluate positions. * @param view Observer, aim, lens, and image size, shared by every frame. * @param seq Frame count and timing (`stepMinutes` or `endJdUt`). * @param opts Per-frame {@link SkyViewOptions} (bortle, refraction, bodies). * @returns A {@link SkyViewSequence}: the frames plus timing and the sky's * per-step rotation about the celestial pole (each frame carries its `pole`). * @example * ```ts * // One frame per 6 minutes for two hours from astronomical dusk * const seq = skyViewSequence(engine, view, { startJdUt: dusk, frames: 21, stepMinutes: 6 }); * seq.rotationDegPerStep; // ~1.5 deg of sky rotation per frame * seq.frames[0].pole; // the rotation center in pixels * ``` */ export declare function skyViewSequence(engine: Engine, view: SkyViewSpec, seq: SkyViewSequenceSpec, opts?: SkyViewOptions): SkyViewSequence;