Day 3: Preproduction (Star Systems)

I don’t really have a codified process of preproduction for projects like this but the logistics and expense of doing a small print run requires more up-front planning than what I’m used to. It’s also an opportunity to look at questions around design and worldbuilding to support the goals of readability and expressiveness.

I have often used this time to work on technical prototypes or build a proof of concept for a new technique or algorithm. That’s not so relevant to this year’s project. I do have lots of interesting new ideas that would be fun to implement but that risks eating into time that would be better spent on storytelling and text effects.

The starting point for building worlds is the stellar and planetary system generator.

I’ve written a few different variations of star and planet generators before and one of the first questions that comes up is around realism and teleology versus ontogenesis.

To put this in context, let’s look at the kind of data that a star system generator might be expected to produce.

Attribute of star Unit of measurement
Temperature Kelvin (K)
Luminosity Solar luminosity (L⊙)
Mass Solar mass (M⊙)
Radius Solar radius (R⊙)
Metallicity Abundance ratio [Fe/H]

We can see intuitively that there are relationships between these attributes. Looking up values and units for the constants and the mass–luminosity relation reveals some of the key concepts.

These are the sort of relations we always want to look for in programming and data modelling. It’s usually more effective and robust to treat a set of related attributes as derived data from a single source attribute and calculate them automatically, rather than manage the set of attributes as independent values that each need to be updated separately. In this case where the relation requires encoding detailed physics knowledge, there might be a complexity and research tradeoff. If these calculations are fiddly to implement and involve lots of edge cases, it might be simpler and faster to manually build each attribute on its own from data lists without directly modelling the relations. We’ll come back to this.

Stars have been traditionally classified into a taxonomy by astronomers based on spectral characteristics, arranged by spectral characteristics. If we simplify this to represent the probability of different main sequence stars occuring for any random sample (assuming a distribution similar to our galaxy), we see M, K and G class stars are by far the most common.

Class Effective temperature Main-sequence mass Probability of occuring
O ≥ 30,000 K ≥ 16 M☉ 0.00003%
B 10,000–30,000 K 2.1–16 M☉ 0.13%
A 7,500–10,000 K 1.4–2.1 M☉ 0.6%
F 6,000–7,500 K 1.04–1.4 M☉ 3%
G 5,200–6,000 K 0.8–1.04 M☉ 7.6%
K 3,700–5,200 K 0.45–0.8 M☉ 12.1%
M 2,400–3,700 K 0.08–0.45 M☉ 76.45%

Figuring out the probability of planets occurring around particular classes of stars is a bit more complicated.

To help keep things manageable, we can discard the massive hot O class stars which will boil and strip away their protoplanetary disks leaving slim chances of any planets forming anywhere near the stellar body. Although planets have been detected around giant blue B class stars, we can reasonably discard them as well. The speed they age leaves a very small window for potential life to evolve. We could treat A class stars similarly to B class stars. Though they’re known to have a high probability of hosting massive Jupiter-like planets, their short lifespans also mean a limited window for life to emerge.

Whether or not to include M class stars is a more interesting question. They’re ubiquitous, long-lived and have a roughly 50% chance of hosting a planet in their habitable zone (notwithstanding the widely discussed problems with tidal locking, variability and violent radiation flaring).

star_classes = [:type_f, :type_g, :type_k, :type_m]

chance_of_occurence = {
  :type_f => 3,
  :type_g => 8,
  :type_k => 12,
  :type_m => 76
}

mass_distribution = {
  :type_f => 1.04..1.4,
  :type_g => 0.8..1.04,
  :type_k => 0.45..0.8,
  :type_m => 0.08..0.45
}

star_class = star_classes.sample
mass = rand(mass_distribution[star_class])

For main sequence stars, the mass–luminosity relation can be described as an approximate power law with the exponent a determined by the star’s mass.

Lower bound Upper bound Exponent
0.08 M☉ 0.43 M☉ a = 2.3
0.43 M☉ 2 M☉ a = 4
2 M☉ 55 M☉ a = 3.5

We can use this relation to get a luminosity value for the star based on a generated value for its mass.

a = case
  when mass <= 0.43 then 2.3
  when mass > 0.43 && mass <= 2 then 4
  when mass > 2 && mass <= 55 then 3.5
end

luminosity = mass.pow(a)

We can do the same for the star’s radius with the mass-radius relation with a discontinuity for k around 1.3 M☉ relating to the convective envelope transition.

Lower bound Upper bound Exponent
0.08 M☉ 1.3 M☉ k = 0.57
1.3 M☉ 55 M☉ k = 0.8
k = if mass <= 1.3
  0.57
else
  0.8
end

radius = mass.pow(k)

I may end up just going with 0.8 for all stars here to keep things simple but I’m happy to have a better understanding of this now then when I started.

The only remaining stellar attribute that I need to think about is metallicity,

Metallicity starts from a single value that represents the overall balance of non-abundant elements in the star’s spectrum. To simplify the code here, we use a basic real interval rather than a logarithmic scale as precise calibration to the presence of iron in our solar system is not needed for this project.

The relationship between gas giant formation and metallicity has clear scientific evidence but a lot more work is still required to establish an understanding of links with terran and neptunian planets.

chance_of_occurence = {
  :type_f => 3,
  :type_g => 8,
  :type_k => 12,
  :type_m => 76
}

For planets themselves, the main factor that determines classification and composition details is their mass–radius relation. Distance from the star is also important when considering details of habitability (for now, we can just assume an oversimplified measurement that doesn’t take orbital eccentricity into account).

Attribute of planet Unit of measurement
Mass Earth mass (M🜨)
Radius Earth radius (M🜨)
Distance from star Astronomical unit (AU)

Research on the mass–radius relation is suggestive of three fixed planetary states that can be generated.

mass-radius-relation

Once a planet gets to a certain scale (somewhere around 1.2 Earth radii), there is an abrupt transition from rocky to a thick atmosphere of hydrogen and helium. This ‘volatile envelope’ line marks the boundary between Earth-like Terran planets and Neptunian planets. A similar phase transition exists with much larger planets where the mass–radius relationship changes to a negative mass exponent (meaning that as the mass grows, the radius shrinks). This ‘self compression’ line is the cutoff between Neptunian and Jovian planets.

PlanetStates = [:terran, :neptunian, :jovian]

MassRadiusExp = {
  :terran => 0.28,
  :neptunian => 0.59,
  :jovian => -0.04
}

def radius_from_mass(mass, planet_state)
  mass.pow(mass_radius_exp[planet_state])
end