July 21, 2020

Pressure II

Update from here.

This time around, since I'm avoiding all manual input as much as possible, I can programmatically generate the pressure bands, at least a rough approximation of them.

The first step is pretty simple, I just apply the rules from this post to hexes which meet the correct criteria.

Then to smooth it out, I apply a box blur with a radius of 10 hexes to the pressure values (which are scaled between -1 and 1). I do this 3 times to approximate a Gaussian blur. Since I'm doing this on a hex grid, I haven't yet figured out any of the clever optimization tricks that are available for this approximation on a square grid.

January (winter in the north)

July (summer in the north)

For my next trick, I will try to avoid memory leaks while calculating wind flow.

July 17, 2020

Currents VI: The Hand of Franklin

With the current code updated, I tackled water temperature and icecap formation.

Warm water from one region will move to a colder region, and vice versa. Where does this happen?

First, I assign a temperature to all water cells based on season and latitude $\ell$. Compared to some of my other functions, it's a little boring: \[T_\ell = \begin{cases}-50 & \text{if $\ell < 0$} \\ -57 & \text{otherwise}\end{cases}\cdot\left(\frac{\ell}{90}\right)^{\begin{cases}4 & \text{if $\ell<0$} \\ 2 & \text{otherwise}\end{cases}}\] This is different from my calculation of air temperature.

Once that is done, I "push" that water around for 80 hexes - an arbitrary number, but whatever. Then I take the average of all the water that has entered a new cell, and there we have the new temperature.


light blue: water that is colder than expected

My next move is to check where the water temperature is below freezing, to see how the ice caps look. I have to do a bit of cleaning here because otherwise I get a whole bunch of unconnected floating bergs - which ain't terribly realistic. But after convincing the water of what I think it should be really doing instead, I get a nice permanent ice cap (dark blue), with a seasonal glaciation shown in light blue.

North Pole


South Pole

The shapes aren't mindblowingly realistic but they'll do for my purposes. Of particular interest are potential harbors which become walled off by icebergs during the cold season, as well as any effects the caps might have on navigational routes: it might be quicker to tempt the poles, but depending on the level of sailing technology it might be wise to steer well clear of even the temporary caps.

Next, I'll revisit wind generation, then return to sea temperature to see where the cold water is affecting coastal climate.

June 27, 2020

All Together Now

As I revisit my generation process from top to bottom (as I do every few quarters), it might be helpful to have a flowchart or at least a process list, both for my own reference and the general welfare of the public.
  1. Terrain generation, either from code or manually input (do not recommend)
  2. Derive currents by a) determining major trade currents at 0 and 45 latitudes, b) extending these currents and splitting them where they hit landforms, and c) interpolating these currents via IDW to make a nice smooth surface. I also have found that d) applying a Gaussian smooth filter to the results is even nicer.
  3. Assign sea surface temperature (SST) and measure the effect of the currents on that temperature: currents from the poles bringing cooler water down to the equator and vice versa.
  4. Generate areas of high and low pressure
  5. From the pressure map, obtain wind direction and speeds
  6. Determine the effect of topography on wind
  7. Apply base precipitation and use the on-shore winds to blow that moisture across the continents
  8. Use the coastal current temperatures and on-shore winds to determine areas of coastal climate influence
  9. Apply base temperature, then modify it according to the coastal climates
  10. Lapse the temperature up mountain slopes
  11. Run Koppen and Holdridge algorithms
At this point, social simulation can take over.

June 25, 2020

Currents V: Refresher

Try to avoid SVGs with a quarter million linked objects, if you can.

In the meantime, the currents are looking nice when applied to the generated terrain surface.

I begin with a handful of hexes in the ocean, on the coasts where the trade winds are blowing more or less perpendicularly into the ocean. I then propagate this current forward (curving around the map folds). When the current reaches an opposite coast, it curves back around.

The next step is to fill in all the gaps using IDW (with nearest neighbor distance $k=20$ and exponent $n=2$), which I've used quite commonly in the past for many applications. It's a really useful little algorithm. This generates a smooth fill.

Lastly, I apply a Gaussian filter to smooth out the sharp bits even more.

This has proven more than adequate for my needs, especially concerning the influence of cold and warm coastal currents, as well as trade and migration routes further down the line. The possibilities present themselves endlessly.

June 23, 2020

Elevation XVII: Lift Up Your Heads

I've been working on the topography generator for quite some time now. I think it's time to put a pin in it. There's still so much to be done, but I need to take a break.

It's looking much better than it was. The endorheic algorithm, which helps basins find their way to the sea, is by far the most inefficient/slow part of the code, and so I think future iterations may try to address that.
GIF of elevation+erosion algorithm at work

This is a good opportunity to start working back through all the rest of the climate generation code to bring life to this map (and by extension, any other generated topography). There is a lot of clean-up to do.

February 6, 2020

Elevation XVI: Clean Your Plate

I'm still not ready to do full tectonic movement simulations.

However, I think I can get some mileage out of using plates themselves to generate a better uplift map. The code for this is pretty simple, some general flood-fill algorithms along the fault lines:


In this model, each hex is only affected by the faults it actually borders:

This gives us some smooth transitions and some stark differences - which I feel effectively captures the action of subducting and overlapping plate faults.

In reality, the difference between ocean and land is not a unimodal curve. Earth differs in this regard because it has uniquely defined oceans in the first place.


However, no one is interested in fitting a curve to this specific shape. Until now.

This consists of a piecewise curve as:
\[
h_1(x) = 55 \exp\left(-\frac{x}{0.65}\right)\textrm{ if }x >= 0
\] \[
h_2(x) = 55 \exp\left(\frac{x}{0.15}\right)\textrm{ if }x < 0
\] \[
h_3(x) = 15 \exp\left(-\left(\frac{x + 4.6}{1.5\sqrt{2}}\right)^2\right)\textrm{ if }x < 0
\]
Where the full cumulative distribution is given by \[H(x) = \int_{-11}^x h_1(n) + h_2(n) + h_3(n)\textrm{ d}n\]

This is in units of 1000 km, which is not what I want. However, if I scale both the inputs and outputs to $[0,1]$, I get a non-linear I/O curve similar to a gamma correction.

Hm.
Doesn't look quite how I wanted it. Lets do a simulation run.
Starting to see some of those shapes: but the distribution is not great. Most of the mountain chains are reasonably distributed except for the big pile in the middle.

I'll continue to work on this. I'm getting closer to what I want.

January 29, 2020

Elevation XV: From Square One

I wanted to take a break from prehistory stuff while I read up on hunter-gatherer sociology. I decided to revisit terrain generation.

My current terrain was created by drawing continents I liked, loosely matching them with tectonics, generating uplift values from that, then performing hydraulic erosion until I liked it. I'm still not ready to move into tectonic simulation, but I wanted some landforms that more closely mimicked the plates I'd drawn. I also took the opportunity to clean up the hydraulic erosion code.

It turns out, perhaps predictably, that the tectonic input map is perhaps the most important variable of all.
Fault map, showing convergent influence in red (with transform in green and divergent in blue)
I experimented with a variety of drop-off functions (Gaussians, other exponentials) to find a good way to find the closest fault (or most influential fault, by strength) to a given hex. This took a really long time, because I had to iterate over ~2000 fault hexes for each of the ~250000 world hexes. Some Poisson sampling helped, but even then, my uplift map ends up with circular ripples.
Uplift map: the Poisson disks are particularly obvious along the convergent equatorial fault
Where the uplift map is too patterned, the landforms tend to be also. So I tried adding in various kinds of warped and ridged noise to try and mix up those forms. One big problem with using noise of any kind is that it looks much too uniform at all scales. A quick look at Earth's coastline reveals a variety of types of "noisy" coastline, some smooth, some rough. So any kind of realistic noise function will need to approximate this variety. The hydraulic erosion discussed below can help with this. However, I still think more work is needed to obtain a better fault/uplift map from the input tectonics. One on-going issue is that the basic tectonic distribution persists from the time when I did most of my work by direct sketching. Maybe next week I'll work a bit on a generated (or at least computed) fault map to add more variety.

There are four basic processes which can alter the altitude of a hex: d$u$, the uplift of the plate; d$e$, the removal through erosion; d$s$, the deposition of transported sediment (from eroded material); d$c$, coastal erosion.

Although I had used a simplified version of this model before, I hadn't considered some practical limits:
  • A hex will stop eroding once it is completely "ground down," that is, its height is equal to the height of the hex to which it drains. At this point, water no longer flows. Hexes under sea-level do not erode with the same mechanism. For now, I've turned it off, but a possible alternative is a constant rate of erosion. As always, erosion rate is determined by how many hexes drain into the target.
  • On the other end, once a hex has been completely filled with sediment, it won't accept any more drainage. This one is a bit more complicated: I compare the height of the hex to the height of all other options that the source hex could use. Once the sediment makes the hex no longer the lowest of all the source's neighbors, drainage will instead flow into the new target. So it might not necessarily reach the height of the source.
  • Coastal erosion stops once the hex has been eroded under sea level. The erosion rate is faster the more sides are exposed to the ocean. I think it might be interesting to continue to transport and deposit that sediment elsewhere, but that would require the current model to run every iteration.
The coastlines look pretty good but the overall landforms indicate I need to do more work with tectonics and uplift

December 28, 2019

Prehistory I

I alluded to this mechanism in the previous post. I want to see how tribal hunter-gatherer societies, with small carrying capacities (around Dunbar's number) spread and grow through time without the added constraints of the full historical generator.

Each tribal polity can only occupy one hex at a time (not a strictly realistic assumption, but we can simply say that there are no other polities of note in the area). Carrying capacity $K$ decreases as resources in the hex are used up. Once $K$ converges with the population size, the tribe must either move or split to maintain balance. When a tribe leaves, the area begins to recover.


There are certain hexes with a high desirability which serve as generators: once a tribe reaches one, there's never any incentive to leave, and so more and more tribes are spun off as a result. When I add discoveries, these will become probable Ur-city locations.

The desirability issue is particularly obvious when a saturation point is reached. I've not yet added oceanic migration, so the tribes bounce around from place to place like so many gas molecules in a closed container.



(I feel compelled to point out that 1500 years is a pretty long time. In 519 AD, the Kingdom of Wessex was founded. In fantasy, we typically deal with deep-time related to people groups - thousand year reigns are not unusual. In real life, empires are generally much more temporary.)

This simulation progresses much faster than the "civilized" simulation, due to code rewrites and the fact that these tribes aren't doing much other than running around and eating all the wildlife.

In these first runs, I noticed that the raw desirability, which is the main selection factor, is far too high for areas that humans (the only race I'm working with for exploratory purposes) would normally avoid, such as deep tundra. So that will need to be fixed. I also want to add a mechanism for tribal warfare, if there is a some greener grass on the other side but it's occupied already.


December 12, 2019

History XXII: R&D

I have long been fascinated by the Worlds: History project. Go check it out.

One idea I'd like to steal gain inspiration from is the discovery system. People groups have chances to learn new technologies over time (or even lose them). It's quite comprehensive in design if not fully filled out.

I love this. Conceptually, it should synthesize with my existing tech tree, yielding at least the following benefits:
  1. Prehistory is now possible; Year 0 doesn't necessarily start with the first city, and every polity doesn't begin with a city/settlement.
  2. Tribes/polities should be able to migrate more rapidly, reaching more area of the planet's surface.
  3. Technology will be different in different areas. This already exists but is fairly deterministic, based on population density. If your density is X, then you have agriculture, for example. This is captured by the tech level.
The downsides? It's a whole 'nother branch of work for me in addition to all of the research I'm already doing. So it might be useful to build it as a toggled mod. Additionally, DrTardigrade is working on timescales many factors larger than mine.

November 13, 2019

History XXI: Cliodynamics IV

Of the tweaking of parameters there is no end.

I've had to suspend some of my own disbelief regarding civilization decay. For the most part, the simulation runs best if civilizations are allowed to grow to their fullest extent. Cliodynamic pressure can collapse a state but not (by itself) remove them from the map.

I also implemented a few changes for armed forces. Recall the distributions of the ages for each race. Each hegemony is then given a standing army based on the number of civilians between the ages of 15 and 50 (relative to human lifespan). This number is further reduced by 50% (since mostly males will fight), 10% (not all will be fit to fight), and 50% again (not all are willing). This article was helpful. Each year, the army is appreciated by new recruits and depreciated by those who die in battle. I'd like to find a way to depreciate retirees as well but without a cellular automation I'm not sure how just yet.

This significantly speeds up the battle simulation as a new military calculation is not required for each battle. In addition, armies can depreciate to the point where they are no longer viable. The amount of troops able to be concentrated at a single point of attack or defense is as follows: \[H = {A \over c} L(d)\], where $A$ is the army size, $c$ is the number of violent conflicts the hegemony is embroiled in, and $L(d)$ is a function of the distance $d$ between the capital and the point of battle. $L(d)$ encodes the army's logistical ability to supply distant points. Large armies can reach further but require additional support. \[L(d) = {1 \over \left({1 + {1 \over s}}\right)^d}\], where $s$ is the support parameter $s=2.82$.

One thing that becomes immediately apparent in the early years is the small troop sizes. A hegemony of approximately 10,000 people may only be able to support an army of 1000 or fewer (and that's at maximum $H$). In the first few hundred years, battle numbers are commonly in the dozens of troops, not thousands.

I tweaked some of the secession mechanics as well. I'd really like to implement some promotion and linearization but I have to think about how best to implement it and whether or not it'd be too computationally expensive. Currently there are still some issues where polities will either fracture into a billion competing hegemonies or glom together into massive, continent-spanning empires.


It's interesting that most polities haven't been able to stick around long enough to reach higher technologies. Most of them end up staying around Tech 6 or 7, which is barely past animal husbandry; a party in these areas would be hard pressed to find any kind of good gear or even wealth. But I suppose glory is always there for the taking.