1// Package nightsky holds the shared types and single-telescope logic for the2// GnoNightSky telescope network.3//4// Note on panics: although this lives under p/, it is application-specific5// logic for the NightSky realms rather than a general-purpose reusable6// library. It deliberately panics on invalid input / failed preconditions7// (the assert-like style described in effective-gno) so the wrapping realms8// don't have to re-check every error. Callers that want softer handling9// should validate before calling.10package nightsky1112import (13 "time"1415 "gno.land/p/nt/avl/v0"16)1718// TelescopeConfig contains the settings and configuration for a telescope19type TelescopeConfig struct {20 Name string // Name of the telescope21 Model string // Telescope model (e.g., "Seestar S50")22 Latitude float64 // Geographic location23 Longitude float64 // Geographic location24 Owner string // Owner's gno address25 RealmPath string // Path to the telescope's realm (e.g., "/r/username/telescope")26 Status string // Current status: "online", "offline", "busy", "error"27 LastUpdate time.Time // Last status update28}2930// AccessRule defines who can access a telescope31type AccessRule struct {32 AllowedAddress string // Gno address allowed to access33 GrantedBy string // Who granted access34 GrantedAt time.Time // When access was granted35 ExpiresAt time.Time // When access expires (zero time = never)36}3738// TelescopeCommand represents a command sent to a telescope39type TelescopeCommand struct {40 CommandType string // "capture" or "stop"41 TargetRA float64 // Right Ascension (for pointing/tracking)42 TargetDec float64 // Declination (for pointing/tracking)43 Exposure int // Exposure time in seconds (for capture)44 RequestedBy string // Who requested the command45 RequestedAt time.Time // When the command was requested46}4748// CaptureResult represents the result of an image capture49type CaptureResult struct {50 ImageURL string // URL to the captured image (e.g., Imgur)51 TargetRA float64 // Right Ascension of target52 TargetDec float64 // Declination of target53 Exposure int // Exposure time in seconds54 CapturedBy string // Who captured the image55 CapturedAt time.Time // When the image was captured56 TelescopeID string // Which telescope captured it57}5859// NewTelescopeConfig creates a new telescope configuration60func NewTelescopeConfig(name, model string, lat, lon float64, owner, realmPath string) TelescopeConfig {61 return TelescopeConfig{62 Name: name,63 Model: model,64 Latitude: lat,65 Longitude: lon,66 Owner: owner,67 RealmPath: realmPath,68 Status: "offline",69 LastUpdate: time.Now(),70 }71}7273// UpdateStatus updates the telescope status74func (t *TelescopeConfig) UpdateStatus(status string) {75 t.Status = status76 t.LastUpdate = time.Now()77}7879// IsAccessAllowed checks if an address has a current (non-expired) access rule.80// rules is an avl.Tree keyed by allowed address holding AccessRule values.81func IsAccessAllowed(address string, rules *avl.Tree) bool {82 if !rules.Has(address) {83 return false84 }85 rule := rules.Get(address).(AccessRule)86 return rule.ExpiresAt.IsZero() || time.Now().Before(rule.ExpiresAt)87}8889// ValidateCommand validates a telescope command90func ValidateCommand(cmd TelescopeCommand, config TelescopeConfig) {91 switch cmd.CommandType {92 case "capture", "stop":93 default:94 panic("invalid command type: must be capture or stop")95 }9697 if cmd.CommandType == "capture" {98 if cmd.TargetRA < 0 || cmd.TargetRA >= 24 {99 panic("invalid RA: must be between 0 and 24 hours")100 }101 if cmd.TargetDec < -90 || cmd.TargetDec > 90 {102 panic("invalid Dec: must be between -90 and 90 degrees")103 }104 if cmd.Exposure < 1 || cmd.Exposure > 300 {105 panic("invalid exposure time: must be between 1 and 300 seconds")106 }107 }108}109110// maxCaptureHistory is the number of captures a single telescope keeps locally.111const maxCaptureHistory = 100112113// TelescopeRealm manages a single telescope's state and operations114type TelescopeRealm struct {115 Config *TelescopeConfig116 AccessRules *avl.Tree // key: allowed address -> AccessRule117 CommandQueue []TelescopeCommand118 Captures *CaptureFeed119 OwnerAddress string120}121122// NewTelescopeRealm creates a new telescope realm instance123func NewTelescopeRealm(config *TelescopeConfig) *TelescopeRealm {124 return &TelescopeRealm{125 Config: config,126 AccessRules: avl.NewTree(),127 CommandQueue: []TelescopeCommand{},128 Captures: NewCaptureFeed(maxCaptureHistory),129 OwnerAddress: config.Owner,130 }131}132133// GrantAccess allows the owner to grant access to another user. Re-granting to134// an existing address overwrites the previous rule.135func (r *TelescopeRealm) GrantAccess(caller, address string, durationDays int) {136 r.checkOwner(caller)137138 if address == "" {139 panic("address cannot be empty")140 }141142 expiresAt := time.Time{} // Never expires by default143 if durationDays > 0 {144 expiresAt = time.Now().Add(time.Duration(durationDays) * 24 * time.Hour)145 }146147 rule := AccessRule{148 AllowedAddress: address,149 GrantedBy: r.OwnerAddress,150 GrantedAt: time.Now(),151 ExpiresAt: expiresAt,152 }153154 r.AccessRules.Set(address, rule)155}156157// RevokeAccess removes access for a specific address158func (r *TelescopeRealm) RevokeAccess(caller, address string) {159 r.checkOwner(caller)160 r.AccessRules.Remove(address)161}162163// SubmitCommand allows authorized users to submit telescope commands164func (r *TelescopeRealm) SubmitCommand(caller, commandType string, targetRA, targetDec float64, exposure int) {165 // Check if caller has access (owner always has access)166 if caller != r.OwnerAddress {167 if !IsAccessAllowed(caller, r.AccessRules) {168 panic("access denied: you don't have permission to use this telescope")169 }170 }171172 // Create command173 cmd := TelescopeCommand{174 CommandType: commandType,175 TargetRA: targetRA,176 TargetDec: targetDec,177 Exposure: exposure,178 RequestedBy: caller,179 RequestedAt: time.Now(),180 }181182 // Validate command183 ValidateCommand(cmd, *r.Config)184185 // Add to queue186 r.CommandQueue = append(r.CommandQueue, cmd)187188 // Update telescope status to busy189 r.Config.UpdateStatus("busy")190}191192// GetNextCommand returns the next command in the queue (for telescope controller)193func (r *TelescopeRealm) GetNextCommand(caller string) TelescopeCommand {194 r.checkOwner(caller)195196 if len(r.CommandQueue) == 0 {197 panic("no commands in queue")198 }199200 cmd := r.CommandQueue[0]201 r.CommandQueue = r.CommandQueue[1:]202203 return cmd204}205206// UpdateStatus allows the owner to update telescope status207func (r *TelescopeRealm) UpdateStatus(caller string, status string) {208 r.checkOwner(caller)209 r.Config.UpdateStatus(status)210}211212// RecordCapture allows the owner to record a capture result213func (r *TelescopeRealm) RecordCapture(caller, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) {214 r.checkOwner(caller)215216 if imageURL == "" {217 panic("image URL cannot be empty")218 }219220 capture := CaptureResult{221 ImageURL: imageURL,222 TargetRA: targetRA,223 TargetDec: targetDec,224 Exposure: exposure,225 CapturedBy: capturedBy,226 CapturedAt: time.Now(),227 TelescopeID: r.Config.Name,228 }229230 r.Captures.Add(capture)231232 // Update status back to online233 r.Config.UpdateStatus("online")234}235236// ClearCommandQueue clears all pending commands (emergency stop)237func (r *TelescopeRealm) ClearCommandQueue(caller string) {238 r.checkOwner(caller)239 r.CommandQueue = []TelescopeCommand{}240 r.Config.UpdateStatus("online")241}242243// GetCommandCount returns the number of pending commands244func (r *TelescopeRealm) GetCommandCount() int {245 return len(r.CommandQueue)246}247248// GetCaptureCount returns the number of stored captures249func (r *TelescopeRealm) GetCaptureCount() int {250 return r.Captures.Len()251}252253// GetAccessRuleCount returns the number of access rules254func (r *TelescopeRealm) GetAccessRuleCount() int {255 return r.AccessRules.Size()256}257258// checkOwner verifies the caller is the owner259func (r *TelescopeRealm) checkOwner(caller string) {260 if caller != r.OwnerAddress {261 panic("access restricted: owner only")262 }263}264Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.