init
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
// Package config holds tunable simulation parameters.
|
||||
//
|
||||
// Units used throughout the simulation: distance in kilometres, time in
|
||||
// seconds, mass in kilograms, force in newtons. Angles are radians.
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"wh/fixed"
|
||||
)
|
||||
|
||||
const SecondsPerDay = 86400
|
||||
|
||||
type Config struct {
|
||||
// Seed identifies the world. The same seed and inputs always produce the
|
||||
// same simulation.
|
||||
Seed uint64
|
||||
|
||||
// Time resolution.
|
||||
TicksPerDay int // must divide SecondsPerDay
|
||||
CyclesPerTick int // VM instructions each ship may execute per tick
|
||||
|
||||
// Ship computer sizes.
|
||||
ProgramBytes int // max program size
|
||||
RAMBytes int // data RAM, includes comm buffers
|
||||
CommBytes int // size of each of the uplink and downlink buffers
|
||||
|
||||
// World.
|
||||
OrbitSpeed fixed.F // km/s; speed of every circular orbit (star pulls with v^2/r)
|
||||
AsteroidCount int
|
||||
BeltInner fixed.F // km
|
||||
BeltOuter fixed.F // km
|
||||
MaxInclination fixed.F // radians; asteroid inclinations are drawn up to this
|
||||
}
|
||||
|
||||
func Default() Config {
|
||||
return Config{
|
||||
Seed: 1,
|
||||
TicksPerDay: 1440,
|
||||
CyclesPerTick: 2000,
|
||||
ProgramBytes: 4096,
|
||||
RAMBytes: 8192,
|
||||
CommBytes: 1024,
|
||||
OrbitSpeed: fixed.FromInt(3),
|
||||
AsteroidCount: 500,
|
||||
BeltInner: fixed.FromInt(1_500_000),
|
||||
BeltOuter: fixed.FromInt(3_000_000),
|
||||
MaxInclination: fixed.FromRatio(1, 5), // ~11.5 degrees
|
||||
}
|
||||
}
|
||||
|
||||
// TickSeconds is the simulated duration of one tick.
|
||||
func (c Config) TickSeconds() int { return SecondsPerDay / c.TicksPerDay }
|
||||
|
||||
func (c Config) Validate() error {
|
||||
switch {
|
||||
case c.TicksPerDay <= 0 || SecondsPerDay%c.TicksPerDay != 0:
|
||||
return fmt.Errorf("TicksPerDay must divide %d, got %d", SecondsPerDay, c.TicksPerDay)
|
||||
case c.CyclesPerTick <= 0:
|
||||
return fmt.Errorf("CyclesPerTick must be positive")
|
||||
case c.ProgramBytes <= 0 || c.ProgramBytes%4 != 0:
|
||||
return fmt.Errorf("ProgramBytes must be a positive multiple of 4")
|
||||
case c.CommBytes <= 0 || c.RAMBytes < 2*c.CommBytes:
|
||||
return fmt.Errorf("RAMBytes must hold both comm buffers")
|
||||
case c.MaxInclination < 0 || c.MaxInclination > fixed.HalfPi:
|
||||
return fmt.Errorf("MaxInclination must be within [0, pi/2]")
|
||||
case c.OrbitSpeed <= 0:
|
||||
return fmt.Errorf("OrbitSpeed must be positive")
|
||||
case c.BeltInner <= 0 || c.BeltOuter <= c.BeltInner:
|
||||
return fmt.Errorf("invalid belt radii")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind registers a flag for each tunable on fs and returns the config that
|
||||
// the flags will fill in when fs is parsed. Each tunable can also be set from
|
||||
// an environment variable (see EnvVars); a flag on the command line overrides
|
||||
// the environment. A malformed environment value is an error.
|
||||
func Bind(fs *flag.FlagSet) (*Config, error) {
|
||||
c := Default()
|
||||
if err := c.applyEnv(os.Getenv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fs.Uint64Var(&c.Seed, "seed", c.Seed, "world seed (env WH_SEED)")
|
||||
fs.IntVar(&c.TicksPerDay, "ticks-per-day", c.TicksPerDay, "simulation ticks per day (env WH_TICKS_PER_DAY)")
|
||||
fs.IntVar(&c.CyclesPerTick, "cycles-per-tick", c.CyclesPerTick, "VM cycles per ship per tick (env WH_CYCLES_PER_TICK)")
|
||||
fs.IntVar(&c.ProgramBytes, "program-bytes", c.ProgramBytes, "max ship program size (env WH_PROGRAM_BYTES)")
|
||||
fs.IntVar(&c.RAMBytes, "ram-bytes", c.RAMBytes, "ship RAM size (env WH_RAM_BYTES)")
|
||||
fs.IntVar(&c.CommBytes, "comm-bytes", c.CommBytes, "uplink/downlink buffer size (env WH_COMM_BYTES)")
|
||||
fs.IntVar(&c.AsteroidCount, "asteroids", c.AsteroidCount, "number of asteroids (env WH_ASTEROIDS)")
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// EnvVars lists the environment variables that set the world, for documentation.
|
||||
var EnvVars = []string{
|
||||
"WH_SEED", "WH_TICKS_PER_DAY", "WH_CYCLES_PER_TICK", "WH_PROGRAM_BYTES",
|
||||
"WH_RAM_BYTES", "WH_COMM_BYTES", "WH_ASTEROIDS",
|
||||
}
|
||||
|
||||
func (c *Config) applyEnv(get func(string) string) error {
|
||||
var err error
|
||||
num := func(name string, dst any) {
|
||||
v := get(name)
|
||||
if v == "" || err != nil {
|
||||
return
|
||||
}
|
||||
switch d := dst.(type) {
|
||||
case *uint64:
|
||||
var n uint64
|
||||
if n, err = strconv.ParseUint(v, 10, 64); err == nil {
|
||||
*d = n
|
||||
}
|
||||
case *int:
|
||||
var n int
|
||||
if n, err = strconv.Atoi(v); err == nil {
|
||||
*d = n
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s=%q: %w", name, v, err)
|
||||
}
|
||||
}
|
||||
num("WH_SEED", &c.Seed)
|
||||
num("WH_TICKS_PER_DAY", &c.TicksPerDay)
|
||||
num("WH_CYCLES_PER_TICK", &c.CyclesPerTick)
|
||||
num("WH_PROGRAM_BYTES", &c.ProgramBytes)
|
||||
num("WH_RAM_BYTES", &c.RAMBytes)
|
||||
num("WH_COMM_BYTES", &c.CommBytes)
|
||||
num("WH_ASTEROIDS", &c.AsteroidCount)
|
||||
return err
|
||||
}
|
||||
|
||||
// DatabaseURL returns the default database location: $DATABASE_URL if set,
|
||||
// else a SQLite file in the working directory. The -db flag overrides it.
|
||||
// Accepted forms are "sqlite:<path>" and "postgres://...".
|
||||
func DatabaseURL() string {
|
||||
if v := os.Getenv("DATABASE_URL"); v != "" {
|
||||
return v
|
||||
}
|
||||
return "sqlite:wh.db"
|
||||
}
|
||||
Reference in New Issue
Block a user