Files
Halcyon/sim/state.go
T
2026-10-01 22:16:48 +02:00

149 lines
3.4 KiB
Go

// Package sim is the deterministic core: given a State, the day's inputs and
// a Config it advances the world by one day. It performs no I/O.
package sim
import (
"fmt"
"halcyon/config"
"halcyon/fixed"
"halcyon/market"
"halcyon/vm"
"halcyon/world"
)
// Hull describes the physical properties of a ship design.
type Hull struct {
DryMass int64 // kg
FuelCap int64 // kg
Thrust int64 // newtons at full throttle
ExhaustVel int64 // m/s
MineRate int64 // kg mined per tick
}
// CommandShip is the only hull for now.
var CommandShip = Hull{
DryMass: 8000,
FuelCap: 4000,
Thrust: 6000,
ExhaustVel: 30000,
MineRate: 10,
}
// Docking / mining limits.
const (
MineRangeKm = 5 // max distance to an asteroid for mining
DockRangeKm = 20 // how close counts as "near the station"
MaxRelSpeedM = 100 // max relative speed (m/s) for mining or docking
StarRadiusKm = 200_000
ScanRangeKm = 500_000 // the scanner sees nothing farther away than this
)
type Ship struct {
ID int64
Owner int64
Hull Hull
Pos fixed.Vec
Vel fixed.Vec
Fuel fixed.F
// Cargo is the hold of the earlier design, before ore travelled in containers.
// It is only read to convert what old ships still carry (migrateLegacyCargo).
Cargo [world.NumOre]int64
CPU *vm.CPU
Alive bool
Earned int64 // credits earned so far
// Peripheral state.
Throttle int32
Azimuth int32
Pitch int32
Target int64
Mining bool
UplinkNew bool
UplinkLen int32
MathX int32
MathY int32
MathZ int32
}
// baseMass is the hull and its fuel; towed containers add to it (State.shipMass).
func (s *Ship) baseMass() fixed.F {
return fixed.FromInt(s.Hull.DryMass) + s.Fuel
}
// State is everything that persists between daily runs.
type State struct {
Day int64
Asteroids []world.Asteroid // sorted by ID
Station world.Station
Ships []*Ship // sorted by ID
// Containers are sorted by ID; NextContainer is the last ID handed out.
Containers []*Container
NextContainer int64
Credits map[int64]int64
Market market.Market
}
// NewState builds the initial world from the config seed.
func NewState(cfg config.Config) *State {
asts, st := world.Generate(cfg)
return &State{
Asteroids: asts,
Station: st,
Credits: map[int64]int64{},
Market: market.New(),
}
}
func (s *State) asteroid(id int64) *world.Asteroid {
// Asteroids are numbered 1..N in slice order.
if id >= 1 && id <= int64(len(s.Asteroids)) {
return &s.Asteroids[id-1]
}
return nil
}
// Launch describes a ship entering the belt at the start of a day.
type Launch struct {
ShipID int64
Owner int64
Program []byte
}
// Uplink is a message delivered to a ship's comm buffer at the start of a day.
type Uplink struct {
ShipID int64
Data []byte
}
// DayInput is everything external the simulation consumes for one day.
type DayInput struct {
Launches []Launch // processed in slice order (must be deterministic)
Uplinks []Uplink
Scraps []int64 // ships to remove from the world before the day starts, in slice order
}
// Downlink is the content of a ship's transmit buffer at the end of the day.
type Downlink struct {
ShipID int64
Data []byte
}
type Event struct {
Tick int
ShipID int64
Kind string
Detail string
}
type DayResult struct {
Day int64
Downlinks []Downlink
Events []Event
Hash [32]byte
}
func (e Event) String() string {
return fmt.Sprintf("t%04d ship %d %s %s", e.Tick, e.ShipID, e.Kind, e.Detail)
}