package plates import "math" // Belt is how faulted a margin's surroundings are: the deformation zone around a boundary, and how densely // it is broken. // // It lives beside the plates rather than beside the faults because it describes a *boundary*, not a fault. // How wide the ground is that a margin deforms is a property of what that margin is doing - a continental // collision takes up its convergence across a belt a thousand kilometres wide and a mid-ocean ridge across an // axis a few tens wide - and the traces are a consequence of that width, not the other way round. What // internal/uplift's belt_faults.go does with these numbers, and the fault map they were read off, is // documented there. type Belt struct { // ZoneKm is the deformation half-width of a *collision* margin closing at ReferenceCmYr, in kilometres. // Every other kind of margin is a fraction of it, and every margin scales with its own rate. ZoneKm float64 `json:"zone_km"` // ReferenceCmYr is the rate ZoneKm is quoted at. Earth's big collisions run 2 to 5 cm/yr. ReferenceCmYr float64 `json:"reference_cm_yr"` // Per1000Km2 is the trace density over the *zone*, not over the planet: a belt is as faulted as a belt is // wherever it happens to run, and the ground away from one is not lightly faulted, it is unfaulted. Per1000Km2 float64 `json:"per_1000km2"` // ThrowM is the total displacement over the whole run, low to high, before the closing rate scales it - // the height of the scarp the fault would build if nothing eroded it. ThrowM [2]float64 `json:"throw_m"` // LengthKm is how long a trace is, low to high, before the local zone width scales it. A wide belt // carries long faults and a narrow one cannot. LengthKm [2]float64 `json:"length_km"` // StrikeSpreadDeg is how far a trace may wander off the belt's local tangent. Small on purpose: a swarm // being sub-parallel is the thing that makes it read as a swarm. // // ConjugateFraction is the share of traces drawn on the second, crossing direction, and ConjugateDeg is // the angle between the two sets. One direction alone reads as corduroy. // // The first two are pointers for the reason Config.SpinFraction is: JSON cannot tell an absent number // from a zero one, and both of these have a real meaning at zero - perfectly parallel traces, and no // second set. Read as the same thing, a block that simply did not mention them silently turned them off, // which is how the first painted planet came out with no conjugate set at all. Absent takes the default; // an explicit 0 means none. StrikeSpreadDeg *float64 `json:"strike_spread_deg"` ConjugateFraction *float64 `json:"conjugate_fraction"` ConjugateDeg float64 `json:"conjugate_deg"` } // DefaultStrikeSpreadDeg and DefaultConjugateFraction are what a belt that does not mention them gets. const ( DefaultStrikeSpreadDeg = 11.0 DefaultConjugateFraction = 0.22 ) // Spread is the configured strike spread, or the default when the block said nothing. func (b Belt) Spread() float64 { if b.StrikeSpreadDeg == nil { return DefaultStrikeSpreadDeg } return math.Max(0, *b.StrikeSpreadDeg) } // Conjugate is the configured share of crossing traces, or the default when the block said nothing. func (b Belt) Conjugate() float64 { if b.ConjugateFraction == nil { return DefaultConjugateFraction } return math.Min(1, math.Max(0, *b.ConjugateFraction)) } // DefaultBelt is what a planet that asks for belt faults but says nothing else gets. func DefaultBelt() Belt { return Belt{ ZoneKm: 6, ReferenceCmYr: 4, Per1000Km2: 90, ThrowM: [2]float64{80, 420}, LengthKm: [2]float64{4, 16}, ConjugateDeg: 32, // StrikeSpreadDeg and ConjugateFraction stay nil: their defaults live in Spread and Conjugate, so // that an explicit zero can mean none. } } // Wanted reports whether this asks for anything. A zero Belt is a planet whose margins are not faulted, which // is what every painted planet had before this existed. func (b Belt) Wanted() bool { return b.ZoneKm > 0 && b.Per1000Km2 > 0 && b.LengthKm[1] > 0 && b.ThrowM[1] > 0 } // WithDefaults fills in the fields that have a sensible value when left out. ZoneKm, Per1000Km2, ThrowM and // LengthKm are deliberately not among them: those four are the feature, and defaulting them would turn // leaving the block out into switching the feature on. func (b Belt) WithDefaults() Belt { d := DefaultBelt() if b.ReferenceCmYr <= 0 { b.ReferenceCmYr = d.ReferenceCmYr } if b.ConjugateDeg <= 0 { b.ConjugateDeg = d.ConjugateDeg } return b }