We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.

To illustrate the temperature adjustment process, I designed a "blocky" land mass with some elevation, and applied the temperature adjustments for values 0, 1 and 2 of the temperature parameter. The results are shown below:

The climate adjustment process war a real pleasure to decipher and understand: somehow it is mimicking the real-world water cycle, accumulating water from the ocean into clouds, which then downpour into rain and shrink as they fly across over earthland...

The exact routine is as follows, processing each map ROW (latitude) twice, from West to East, then from East to West:

For each map row (y from 0 to 49):

Initialize the row "wetness" to 0 (wetness can be seen as clouds accumulating humidity...)

Initialize the row "latitude" to |25-y|, i.e. distance from the equator

Firstly, process each row square from West to East (x from 0 to 79):

If the square is OCEAN, accumulate clouds:

compute the square "wetness yield" as |latitude-12|+climateParameter*4 - note: somehow, this yield is the highest for latitude 12, i.e. temperate regions between poles and equator; it is also boosted by the climate parameter, quite logically
[*] if the square wetness yield is higher than the current row wetness, then increase the row wetness by 1

[*] Else (if the square is not OCEAN), consume clouds:

compute a random "rainfall" value between 0 and (7 - climateParameter*2); note: we see here that for wet climates (param = 2), the high random bound is lower (7-2*2 = 3), so the consumption of wetness to transform a square is lower as well, and more squares can be humidified...

substract the rainfall value from the row wetness

convert the land type as follows:

PLAINS becomes GRASSLAND

TUNDRA becomes ARCTIC

HILLS becomes FOREST

DESERT become PLAINS

MOUNTAINS do not change, but additionally decrease the wetness by 3

[*] Reset the row "wetness" to 0
[*] Then, reprocess each row square from East to West (x from 79 to 0):

If the square is OCEAN, accumulate clouds:

compute the square "wetness yield" as latitude/2+climateParameter - note: this yield is different from the previous one, somehow it the highest at the poles...

if the square wetness yield is higher than the current row wetness, then increase the row wetness by 1

Else (if the square is not OCEAN), consume clouds:

compute a random "rainfall" value between 0 and (7 - climateParameter*2); note: same as previously

Understanding the river process was a little more challenging, and there's part of it that is still a mystery to me - but it is not directly related to the generation of the map geography... Rather, it is the code that writes pixels with value '8' inside the 'improvement' layer. The purpose of this value has yet to be understood.

Anyway, for the generation of rivers themselves, here is how it goes:

Initialize the river count to 0; this is the number of rivers generated

The routine below either loops for a maximum of 256 times or stops as soon as the number of generated rivers is above ((climateParam + landMassParam)*2 + 6):

First create a backup copy of the whole map, which can be restored later if the river generation fails

Initialize the river length to 0

Initialize variable A to a random value in the range [0..3] then multiply it by 2; this makes the value of A taken randomly within {0,2,4,6}; thanks to Renergy for helping fix this

Randomly select a HILLS square on the map

Set the currently selected map square to RIVER type

Check whether any of the 4 direct neighbour squares is of type OCEAN, that is N, E, S or W, and store this info as a flag 'ocean nearby'

Initialize variable B to the same value as A

Compute random variable C in the range [0..1]

Update variable A as: A = ( ( (C - riverLength%2)*2 + A) & 0x7 ); as Renergy thoughfully put it, this is actually a "river downflow" heuristic for choosing the next river square, that can be expressed as: randomly choose to either go ahead or turn at a right-angle; if river length is even, the right-angle turn is to the right (clockwise), otherwise if the river length is odd, the right-angle turn is to the left (count-clockwise)

The next step is the mystery one:

Set B = 7-B

If ( B <= A ) then set the Improvement layer square (x,y) to value '8' ; I can't exactly figure out the meaning of this yet...

Increment the riverLength by 1

Select neighbour map square with id (A+1); as A is in the range [0..7], A+1 is in [1..8] and represents 1 of the 8 direct neighbours of the current square

If the previous map square was NOT next to an OCEAN, and the currently selected map square is neither OCEAN, RIVER or MOUNTAINS, then loop back to step 5.

Else if the (previous map square WAS next to an OCEAN, OR the selected map square is RIVER) AND the riverLength is equal to or above 5:

Increase the River Count by 1

Transform all FOREST squares into JUNGLE, within the 7x7-square area centered on the map square

Else, discard the current river, and rollback the map geography to its previous state

This process is a bit hard to apprehend at first, but gets clearer after several passes... I also belive there may be additional processing afterwards regarding the '8' values written in the Improvement layer...

Hereunder is a series of pictures illustrating the 5-step process from Land Mass generation to River:

A hypothesis, or perhaps couple of "brainstorming" notes, regarding river generation.

First - how the "neighbour map square with id" is to be interpreted? I suppose/deduce in the following that it is something like 0=north; 1=northeast; 2=east; 3=southeast; 4=south; ....... 7=northwest? I.e. starting from north (zero) and going clockwise:

7|0|1
6|_|2
5|4|3

The A value seems like A="current riverflow direction" (B is used for previous/upstream riverflow direction)

Which could mean "turn left or maintain direction or turn right with 1:2:1 probability"; i.e. "direction = (direction + random{-2,0,0,+2}) mod 8"

Why?

(x+y) & 0x7 is the same as (x+y) % 8 - addition modulo eight, hence "clock-like" behaviour. This is best seen if 0x7 is written in binary - 111b. The & operation "cuts anything above third bit" (producing the remainder). Note: the possibility to calculate the remainder in this way is limited to powers of 2 only (using bitmask 0x3=11b for mod 4, 0x7=111b for mod 8, 0xF=1111b for mod 16 etc.)

PS: What is the convention for the Y coordinate on map? I think zero is on the top (to correspond with VGA mode 0x13 addressing, which iirc is (0,0) topleft, (320,200) (319,199) bottom right). Maybe this should be added somewhere as an info "for future generations"

PS: What is the convention for the Y coordinate on map? I think zero is on the top (to correspond with VGA mode 0x13 addressing, which iirc is (0,0) topleft, (320,200) bottom right). Maybe this should be added somewhere as an info "for future generations"

Click to expand...

Yes I didn't even take the time to explain those: (0,0) is the top-left corner of the image, and to be exact, the bottom-right pixel is ... (319,199)

Thanks for the reply. Just a small coincidental example - which actually ran through my mind when looking at the algorithm (not that I wouldn't trust disassembly) - CIV is indeed able to produce "circular rivers", see the attached pic from a legitimate randomly generated map.

Thanks for the reply. Just a small coincidental example - which actually ran through my mind when looking at the algorithm (not that I wouldn't trust disassembly) - CIV is indeed able to produce "circular rivers", see the attached pic from a legitimate randomly generated map.

Click to expand...

This makes sense based on the algorithm: at every odd step it turned at right-angle, and at every even step it continued straight ahead, until it stumbled upon the first river square, which is a condition for terminating the river... And its length being more than 5, it is kept.

This makes sense based on the algorithm: at every odd step it turned at right-angle, and at every even step it continued straight ahead, until it stumbled upon the first river square, which is a condition for terminating the river... And its length being more than 5, it is kept.

Click to expand...

You are completely right about the allowed direction of the turn being tied to the riverlength parity. The algorithm/formula disallows two consecutive turns in the same direction.

I haven't been keeping up with the forum much since relocating for work in another country, but I just have to say... This was absolutely fascinating! What great appreciation we can have now for the original designers' creative approach (which fancied to be "realistic" in a pragmatic way). Wonderful archaeology you have done darkpanda. Amazing, just fascinating!

Actually, in JCivED you can use the standard 4 "custom earth" variables (land mass, temperature, climate, age) using Civ's values of 0 to 2... But you can also set value outside this range, which can yield strange results.

I plan to post a new thread that will explain various ways to hack Civ in order to alter the random map generation (more massive land, more "islands" worlds, etc.) and make JCivED support this kind of alteration as well... It's on my ToDo List

[*] Firstly, process each row square from West to East (x from 0 to 79):

If the square is OCEAN, accumulate clouds:

compute the square "wetness yield" as |latitude-12|+climateParameter*4 - note: somehow, this yield is the highest for latitude 12, i.e. temperate regions between poles and equator; it is also boosted by the climate parameter, quite logically
[*] if the square wetness yield is higher than the current row wetness, then increase the row wetness by 1

[*] Else (if the square is not OCEAN), consume clouds:

compute a random "rainfall" value between 0 and (7 - climateParameter*2); note: we see here that for wet climates (param = 2), the high random bound is lower (7-2*2 = 3), so the consumption of wetness to transform a square is lower as well, and more squares can be humidified...

substract the rainfall value from the row wetness

convert the land type as follows:

PLAINS becomes GRASSLAND

TUNDRA becomes ARCTIC

HILLS becomes FOREST

DESERT become PLAINS

MOUNTAINS do not change, but additionally decrease the wetness by 3

[*] Reset the row "wetness" to 0
[*] Then, reprocess each row square from East to West (x from 79 to 0):

If the square is OCEAN, accumulate clouds:

compute the square "wetness yield" as latitude/2+climateParameter - note: this yield is different from the previous one, somehow it the highest at the poles...

if the square wetness yield is higher than the current row wetness, then increase the row wetness by 1

Else (if the square is not OCEAN), consume clouds:

compute a random "rainfall" value between 0 and (7 - climateParameter*2); note: same as previously

In the step where we go from west to east, we calculate a row wetness but we don't do anything with it. In fact, as far as I understand it, the only thing that happens here is that Plains, Tundra, Hills and Desert are replaced with Grass, Arctic, Forests and Plains. Why? Am I missing something here?

In the step where we go from west to east, we calculate a row wetness but we don't do anything with it. In fact, as far as I understand it, the only thing that happens here is that Plains, Tundra, Hills and Desert are replaced with Grass, Arctic, Forests and Plains. Why? Am I missing something here?

Click to expand...

In fact, my description is not very thorough in the details, but the principle of the "row wetnees" is that you accumulate water into clouds as you're hovering the OCEAN squares (row wetness increases), then you consume the clouds while hovering LAND squares (row wetness decreases).

This what the following sentence means: substract the rainfall value from the row wetness

Or, with a formula: row_wetness = row_wetness - rainfall_value;

In addition, what is not described is that you can only consume row wetness if it is strictly above 0, obviously... If all the "wetness" (all the clouds) are already consumed, then you just skip the land transformation.

I've been working on a Civ4/5/6 clone, and decided to try modding it to run traditional Civ1. Most of the features have been quick and easy to add, but the map generation seemed like it would kill me - I don't even use the terrain types from Civ1 (in my designs, Forest is an improvement that sits on Grass or Plains; Mountains are a special kind of Hill; etc).

So this thread made all the difference . Thanks to the awesome work of everyone here (and I enjoyed going down the rabbit hole and reading the SVE threads too).

Here's the output after implementing the first 3 stages (basic land generation, climate change, and wetness):

...and the same with the 4th stage (erosion) added:

No rivers for now - rivers in 3D are much harder to do and I'm still working on a photo-realistic river renderer - but this is enough for me to start playtesting Civ1 with. Thanks @darkpanda et al!