Wednesday, October 23, 2019

Faster worlds!

Well, it's been a while, but things have been happening.

I've just about finished rewriting the global generation functions. The revised version of Undiscovered Worlds can now generate global maps just as the original did, complete with terrain, climate, rivers and lakes, and so on. Here are some examples:

As you can see, these maps look very similar to those of the original version - as you'd expect given that I've mostly been translating the same routines. I've tweaked some of them here and there though, and also other changes (features!) have appeared without my intent. For some reason, the new version has more of a tendency to produce long island chains, which I like. It also has a tendency sometimes to leave areas of sea unconnected to the ocean. I'm not sure why, because it's supposed to remove all such areas, but I don't mind leaving it as the occasional disconnected sea isn't a bad thing (like the Caspian Sea).

The main difference is the massive decrease in generation time. The old version would take somewhere between one and two minutes to create worlds like these. As you can see, these ones are popping out in round about seven to fourteen seconds. The quickest I've seen so far is five seconds, the slowest is eighteen. I think this alone makes it worth doing this port as the app becomes so much more useable.

I'm wondering whether it would be worth increasing the size of the global maps. At the moment, one pixel represents 32km. I could double the width and height and make it so that one pixel represents 16km. That would mean a corresponding change in the regional maps - previously, one pixel there represents 2km, but if I were to change the scales, that would mean that one pixel would represent 1km. I wouldn't change much about the actual generation, because to be honest I think that that scale would more accurately represent the size of mountains etc. that I already have. It would slow down the world generation, of course - presumably quadruple it - but the extra level of detail and realism might make that time cost worth it.

Anyway, the next step is to create the regional map level! That's where an increase in speed will really help, so I'm hoping it will come out well. Time to get grappling.

Wednesday, July 31, 2019

On the way


That may not look very exciting but I'm pretty pleased nonetheless. I've rewritten my fractal-generating routine in C++. For the most part it was fairly straightforward - much more so than writing the original from scratch - and as expected it's satisfyingly fast, taking approximately 1/5 seconds to generate. I've rebuilt the structure of Undiscovered Worlds in C++ using SFML for the graphical interface etc., which makes for a (to me) pleasingly old-school look. The fractal routine is the first bit of actual world-building I've reprogrammed. Obviously there's an awful lot more stuff to put in before it comes close to what I've got in the original version, even just at the world map level, but I'm happy that it seems to be working so far.

I won't keep posting all the updates I make that are just recreations of what I already have, as that doesn't sound enormously interesting, but I'm hoping to make some reasonable progress behind the scenes.

Monday, July 1, 2019

Where next?

I've been adding some minor tweaks, mainly to colder areas. I've implemented glaciers, which are basically frozen rivers. Glaciers appear where the temperature is lower than a certain threshold, and they differ from normal rivers in being wider - they fill the whole of what would, in a normal river, be just the river valley. So we get features like this:

I don't think it's completely realistic but it is at least an improvement on having actual rivers running through frozen areas like this.

I've also made the colour gradations smoother on those maps where tundra gives way to ice:

It's not ideal but it's a bit prettier than the more abrupt transitions that areas of this kind used to show.

With these additions I’ve got pretty much all of the terrain features into Undiscovered Worlds that I was planning. So what do I do now?

It could go in a number of directions:

(1) I could create a smaller-scale map level - local level - and work on that. Just as the regional map expands small areas of the global map and creates new detail, so too the local map would expand small areas of the regional map. This would allow us to explore these worlds in much finer detail, and it would take us a step closer to being in a position to create 3D renditions of these landscapes too.

(2) I could leave terrain as it is and start work on the whole next phase of Undiscovered Worlds - creating procedurally generated ecosystems of both flora and fauna to populate these worlds. As I've said before, I think this is potentially the most interesting and distinctive aspect of this project, assuming it actually gets somewhere.

(3) I could put the development of new features on hold while I convert what I’ve got now into a more efficient language.

(1) and (2) are more immediately interesting but I'm mainly inclined to focus on (3) now. I started the project purely to see how far I could get with generating vaguely plausible world maps and, as the saying goes, the tale has grown in the telling. I've got over 45,000 lines of code now, and although the results are very far from perfect, I'm really pleased with the look and feel of the maps it's making. I didn't expect to be able to get this far. But because I was doing it just for fun, I did it in a variant of BASIC because that's what I've been familiar with ever since C64 days (actually ZX81 days!). But it's really not very suitable, because this is a very calculation-heavy program that gets really slow in a language like that. For example, take the following random regional map that I just did experimentally:

That took 55 seconds, which is ridiculous really.

I'd like to turn this into something that's fit for other people to play with, not just me. So before I expand it further I want to try to take the methods I've developed and implement them in a more suitable language. I'm going to try reprogramming it in C++, probably using QT for the interface. This may well fail completely, in which case I'll go back to my slow, inefficient process that does at least work after a fashion, and move into (1) or (2) above. But it's good to try to develop new skills and stretch myself with this project, so I'll give it a go and see what happens. If it works then I hope to be able to replicate the functionality I've already got but with much greater speed and efficiency. In which case there may be some interruption to the blog (I'm guessing no-one wants to hear about me doing everything again), but whatever happens, the project is advancing one way or another, whether I'm posting or not!

Thursday, June 6, 2019

Delta blues

Most of the main landscape features that I was planning at the global and regional levels are now in Undiscovered Worlds. There's one big one left though: river deltas.

Deltas are caused where a river flows into a larger body of water (usually the sea), and where the river is slow-moving enough to deposit the silt that it's accumulated over its course. The silt is dumped in the sea and creates new land, with the river splitting up to go around it. Islands appear in the river and you get lots of wetlands. And obviously deltas are very striking seen from above:

So we want to try to replicate them in UW.

Now as we know, rivers in UW emerge from flow between cells. Any given land cell can receive flow from any of its eight neighbours. And it sends outgoing flow - calculated as the total of the incoming flows - to a single one of those neighbours, usually the lowest one. This is a perfectly reasonable way of modelling the usual flow of water over land, but it can't handle deltas, because these are points where rivers split up and send outgoing flow in more than one direction.

So what can we do? I spent much time thinking about this and decided: we need a whole new river array. This one will be just for delta branches. As with the normal river array, each cell will have a direction and flow volume (for both January and July). But they will run in reverse.

Here's how we calculate it. At the global level, we go through the mouths of all the rivers and work out how likely they are to have deltas. Basically, I assume that the probability of a delta appearing is a function of the tidal range of the river mouth, the size of the river, and the steepness of the terrain.

Once we have a river mouth that we want to deltafy, the next step is to find locations for the mouths of the delta branches. This is reasonably straightforward. We put these in the sea, next to the coast, like the endpoint of the river, but we displace them along a line (roughly) parallel to the coast. The result is something like this:

The white squares are the end points for the branches of the delta. Bear in mind that what we're doing right now is all at the global level - I've just marked them at the regional level because it's much easier to see there. Some of them are a little further out to sea than others, but that doesn't really matter because all rivers run into the sea to some degree and we just don't show those bits.

Now we choose a point a few cells upstream. This, the "origin point", will be the point at which the river turns into a delta.

Then we pretend that the branch mouths are actually the beginnings of rivers, flowing towards the origin point. So all we have to do is take each branch in turn and trace it from its mouth up to the origin point. Obviously we can't do this like normal rivers, going downhill. Instead, we aim for the origin point but allow some deviation along the way. If we hit one of the existing branches, we simply add this branch's flow to that of the existing branch and follow it up to the origin point, just as we would for a normal river that flows into another one. (The flow for each branch is easy to calculate - we just divide the flow of the original river by the number of branches.) The result is a network of branches flowing together and coalescing as they near the origin point - which looks, in reverse, exactly like a river splitting into branches which then split further as they near the coast.

We also reduce the flow of the river downstream from the origin point so it's the same size as the delta branches.

Finally, we flatten all the land on the delta branches, the reduced river, and surrounding cells so it's only slightly above sea level. (This was actually tricky to do as it led to the river in that area carving out great big estuaries on the regional map, which I don't want where deltas are meant to go. It was harder to stop it doing that than you'd think and eventually I gave up and put in another bodge to just raise all areas of sea near deltas to be above land.)

If we use white squares on the regional map to mark the tiles that contain delta branches, we now have this sort of thing:

There are a few annoying artefacts from the flattening, but overall this looks good - already we have shapes that look roughly like river deltas should.

So we've got the routes of our delta branches more or less sorted at the global level. Now it's just a matter (just! ha!) of drawing rivers on the regional map where these branches go. Basically, we treat the delta branches as an almost completely independent river system. That means that they don't deliberately interact with existing rivers passing through their tiles - they might run near them or even cross over them, for example. Ordinarily this would be anathema but we want that confused, jumbled look of a river delta where streams merge and split all over the place. The exception to this of course is at the origin point, where we want the delta branches to meet up with the main river so it looks like it's splitting into them. And as before, we treat them as if they're running from the sea to the origin point. We draw them on a separate delta map, and then when they're all done, we copy them onto the normal regional river map, reversing the flow directions as we do so so that they appear to be flowing towards the sea.

Needless to say, getting this working required a lot of jiggery pokery and dealing with wildly unexpected problems. But I'm quite pleased with the results (note the wetlands too):

As always, they're not perfect, and there's still a fair bit to do on smoothing out some of the code and correcting weird artefacts, but I think they look pleasingly chaotic and also quite varied in the patterns they trace.

Wednesday, May 8, 2019

The canyons of your mind

I previously explained that when rivers are carved at the regional level, they are placed lower than the base level of their tile's elevation. The amount by which they are lowered is a constant throughout the whole world - I've found 20 metres to give generally good results.

This gives us some variety, as depending on how wide the river valley and how mountainous the area is, we get quite different-looking valleys. But still it would be good if we could vary the constant itself, to give, say, really deep valleys sometimes - and some epic canyons:

But doing this is tricky. For one thing, if the constant varied, then there might be cases where a river runs uphill (if it moves from one tile to another which is only slightly lower but has a much smaller constant). For another, lake surfaces are calculated on the basis of this constant, and if it varies then all sorts of complications would arise.

So how can we get really impressive canyons? My cunning answer to this is: instead of lowering the river, let's raise the land around it. Suppose, during the global map creation phase, we introduce artificially raised areas. These will be superimposed over the normal elevation map - that is, the "real" elevation will be retained on the standard elevation map, but there will be a new array which has, in some places, higher elevations marked. We'll use those when calculating climate and so on.

As always, the rivers will still be calculated on the basis of the normal elevation map (without mountains), not these higher elevations. And when we go to the regional level, the rivers will still be given elevations based on that normal elevation map, lowered by the same constant as usual, just as they are now. But the terrain elevation around them will be based on the higher elevations from our new array (if any are present for that area). This should give the effect of rivers (and river valleys) that are significantly lower than the surrounding terrain, and we might get some nice canyon systems as a result.

First, we create the "extra elevation" array. This is just another fractal, but with lower values so that a lot of it is zero (we don't want extra elevation everywhere, and certainly not over sea). That comes out looking like this:

The highest value here is 500. The sharp edges are coastlines (no matter how high the extra elevation over land, it's always zero over sea). I've applied a blur to it so that it's not too sharp at the coasts - we don't want gigantic cliffs all along them, at least not most of the time.

Now we apply this to the global map. The values in the extra elevation map are simply added to the values in the global map (with mountains). We don't add them to the global map (without mountains) though, because that's the one we'll be using to calculate river elevations from.

That gives us a global map that looks like this:

For the most part, the change is very subtle. The only part of this map where you can clearly see the land is raised as a result of the extra elevation is on that large peninsula jutting off the eastern coast of the big continent just to the right of centre. About half of that peninsula has high extra elevation and it does appear notably elevated on the adjusted map.

So we have our extra elevation applied to the map. Now it's just a matter of making it have the desired effect at the regional level. At the moment, the elevations at the regional level are based on the global map without mountains. All we have to do is change it so that they are based on that no-mountains map plus the extra elevation map. The river elevations, meanwhile, continue to be based on the global map without mountains alone. That should mean that where the extra elevation map is substantial, the rivers will appear to be much lower than the surrounding terrain.

So I first had a look at that peninsula mentioned above, and this is the first map I got:

This is pretty much what I was hoping for. The rivers are carving valleys which are much deeper than before. Because UW only marks rivers of greater than a certain size on the relief map, but all rivers carve valleys, you can see some valleys without obvious rivers in them, giving this terrain a very scored sort of look. You can see the effect on the elevation map of the same region too:

Remember, of course, that the larger the river, the wider a valley floor it has around it. So all the rivers are equally prone to carving deep canyons where there is substantial extra elevation, but only the larger rivers will carve really wide ones.

Zooming in, we get tiles like this:

This is a fairly substantial river (flow of around 1000 cubic metres per second). The terrain to the north of the highlighted square is around 1100 metres above sea level, dropping to 620 on the valley floor. The drop is more precipitous on the north bank than on the south, where the land slopes up in a relatively shallow way to the heights. Why is this? Because the terrain of each tile is based on seeds at the corners of the tile. Here, the elevation of the river has been taken directly from the no-mountains global map. The elevation of the corners of the tile has then been taken from that map plus the extra elevation. The computer has tried to smooth the terrain from the corners down to the already-calculated elevations at the river cells. There is less distance to the northeastern corner than to the southeastern corner, so it's steeper in that direction.

This tells us that to get a real Grand Canyon, with precipitous sides all around to a great height, we'd need a tile where we not only have substantial extra elevation but also rivers flowing in from all directions to ensure that the distances to the terrain seed cells are relatively short, creating steep slopes all around.

For comparison, here's a region where rivers from an area with high extra elevation flow to one with none:

So we can imagine that the high ground in the northeast of this region is made of relatively soft rock, where the rivers have worn away deep valleys as they flow to the lowlands.

One more region I thought was interesting. Here's the river map first:

Look at that river with lots of small tributaries in the north that then flows roughly southeast and reaches the coast about halfway down the image. The elevation map shows what's going on here:

Those tributaries are flowing into a fairly wide depression in the surrounding higher land. But the river they form then flows out of that depression in a deep valley through that higher land, until it emerges to the flatter coastal shelf and flows into the sea. The relief map looks like this:

So the highlands it flows through are actually a big desert. We can imagine this wide, relatively fertile valley, several kilometres across and about 300 metres lower than the surrounding desert. Perhaps camel-mounted nomads journeying across the desert might crest the top of a hill and see the welcome valley suddenly emerge into view...?

So overall I'm quite happy with how this has worked. There will probably need to be more tweakings of the settings of this (I suspect that higher extra elevation might produce good results) but the basic principle is working well, which makes a nice change.

Wednesday, May 1, 2019

Sand and swamps

It's been a little bit of a while since I last updated. This is partly because of real-life business, but mainly because I've been bogged down with trying to improve lakes, along the lines described in the previous post. The salt lakes are looking good but I want the freshwater lakes to work similarly - but I just can't get it to work, no matter how many nifty tweaks I make. Infuriating! Well, the existing lake system is OK most of the time, so I'll leave it in place for the time being, because I want to move on.

So enough of lakes - at least for now. Let's add some more new things.

First, ergs. Not the Jacob Rees-Mogg kind, fortunately. I mean the desert kind - large areas of sand dunes of the kind that we normally think of when we think of "deserts". In reality of course deserts vary in terrain type, and dunes cover only some parts of deserts. A large area of sand and dunes is an erg, and they form discrete areas in deserts such as the Sahara:

So let's put them in Undiscovered Worlds. This is pretty straightforward. We already did salt pans, which are also large, flat expanses of unusual terrain that occur in deserts. So all we have to do is to repurpose that code and do dunes in exactly the same way. The result:

Salt pans are lower than the surrounding area, because they're like lakes; this isn't the case with ergs, but otherwise they're functionally pretty much identical to salt pans other than the name, colour, and size/frequency (I've set it so these are more common and tend to be larger). There's no need to try to represent the dunes themselves because the scale is wrong - you wouldn't be able to see them at this scale. If I manage to create a local scale, more zoomed in than the regional, I'll have to find a way to make realistic dunes on these areas.

I think our deserts are looking pretty well stocked now. Time for something quite different: wetlands. These are areas that are not exactly lakes/rivers but not exactly solid land either, either because they are frequently flooded or because there is always standing water there. There are four kinds of wetland, distinguished mainly by vegetation type: swamp, marsh, bog, and fen. Swamps have trees in them, marshes have bushes or grass in them, and bogs and fens have small shrubs, grass, and moss, that turn into peat. (Fens tend to be fed by groundwater while bogs are more dependent on precipitation.) For our purposes, these distinctions don't really matter, at least for the time being. We'll treat them all simply as wetlands, but we will distinguish between freshwater, brackish, and saltwater wetlands, based on proximity to sea/salt lakes/salt pans.

We'll mark wetlands on the specials array. At the global level, it's just a matter of taking into account the following factors: proximity to water; level of precipitation; flatness of terrain; and what I'll call "drainage", following the example of Dwarf Fortress, but is really just another global-scale fractal to add some variation. The higher these factors, the more likely any cell is to be marked as wetlands. Also they are much more likely in tundra areas, because the frost melts in the summer and creates areas of standing water. If a wetlands cell is next to a salty cell of some kind it will be saltwater wetlands, and if it's two cells away it will be brackish; otherwise it will be freshwater.

That yields a world map something like this (wetlands marked in cyan just for the sake of clarity):

At the regional level, for any tile whose source cell is wetlands, we go through it and paste a blob of wetlands centred on each river, lake, or coastal cell. The elevation of each blob is taken from the elevation of the central point, to ensure that the wetlands remain roughly flat. This leads to what I think is a rather nice effect where wetlands cluster around the hydrological features but in quite a "bitty" sort of way. Here's what we get in a large riverine system in a rainforest climate:

For comparison, here is a map of the wetlands of the Amazon basin:

The look I've got isn't exactly the same, of course, but I think it's plausible enough to be getting on with. A little fiddling with the colours to make it all blend in and we get some nice subtle swampy areas like this:

We've also got quite a lot of wetlands in tundra areas, as noted above. Where these coincide with areas with lots of glacial lakes we get some pleasantly interesting looking landscapes:

So that's that. Onwards and upwards!

Wednesday, March 27, 2019

Better basins

Although the endorheic lakes are working fairly well, I wasn't really happy with the river networks around them, even on those occasions when there weren't any bugs:

While there aren't exactly any problems with this map, I think the drainage area for this lake should be wider. Endorheic lakes typically appear in the middle of large arid areas where all the water flows to a central point. So those other rivers that pass fairly close to this lake, but flow past it and ultimately into the sea, look a little odd.

As described in the previous post, endorheic lakes are made in two stages. First, during the river calculation stage, we place small areas of sea at random points where rivers are flowing through deserts. We also adjust the flow directions of the surrounding cells to ensure that they are all flowing into these patches of sea. Second, after all the rivers have been done, these small seas are turned into proper lakes. But a lot of the problems I've been having have been associated with the redirection of rivers into lakes. So it occurred to me: why not create those small areas of sea before any rivers have been placed at all? Moreover, what if, at that same stage, we alter the shape of the land for quite a way around to ensure that all the rivers naturally flow into that patch of sea, during the river calculation phase, without having to be artificially routed into it?

So I tried this. First, the program creates those small areas of sea after the precipitation has been calculated (so it knows to put them in dry areas) but before any rivers have been calculated. Then we have a new routine to create a depression around each one. It's pretty simple: it moves outwards from the lake in a series of concentric circles, each time checking to see whether the elevation of each cell in that ring is higher than a set amount. Any cell that is too high is lowered to that set amount. Each time the circle widens, the set amount is raised a little. The program goes outward from the sea, slowly raising the maximum elevation, until no cells need to be lowered, and then it stops. The effect is as if an enormous saucer were gently pressed into the land around the lake, creating a wide bowl-like depression in which any rivers will naturally run into the lake at the centre. And then, as before, once all the rivers are done our patches of sea are turned into salt lakes. However, they are made much lower in elevation than they were before, so they sit nicely at the bottom of their basins.

I found that making the basins this way tended to result in rather straight rivers running down them, so I added some variation in the heights as the basins are made - and then had it do the Planchon-Darboux algorithm yet again, to ensure that no new depressions were created within the basins (other than the one at the centre, of course, which we actually want).

This gets us global river maps like this:

You can see (hopefully) that some of those isolated lakes have got quite extensive systems of rivers draining into them, like spiders sitting in the middle of large webs.

However, this did cause some annoying problems (of course) at the regional level. Some rivers stopped just short of the lakes, like this:

I think that this was because, when creating the rivers at the global level, UW was unable to run these ones on for some cells into the sea, as it normally does, because the sea was so small. At the regional level, the rivers therefore stop when they reach (what was originally) the sea tile, but because the coastline of the lake doesn't map perfectly onto the boundary between tiles, it may not reach the actual lake. (This is just a guess. It may be caused by something else entirely. Who knows?)

I spent quite a lot of time trying to rectify this, either by tinkering with the global river calculation or by adding a routine at the regional level to detect these unfinished rivers and finish them. Eventually I went with a solution of the latter kind, but it's a real bodge job. It ought to detect the nearest lake or sea cell and create new river directly to it, but for some reason this just won't work. So now it just continues the river in the direction it was originally going in until it hits lake or sea. This isn't really ideal, because perhaps it wasn't going in a direction that would hit lake or sea (although you'd think it would usually be). Still, this bodge works for now, and perhaps I'll be able to see what's wrong with the superior method when I'm less tired.

So now I'm getting salt lakes like these (both relief and river maps, for clarity's sake):

I like this one, where a couple of the rivers flow through a rift lake and then subsequently drain into a salt lake:

There is another issue. Previously, we could guarantee that any endorheic lake would have at least one river flowing into it, because they were created on rivers during the river calculation phase. With this new method, the lakes are placed before any rivers have been done at all, so there's always a chance that no river will flow into the lake, even with the wide basin around it. Since it's being placed in a desert, this may be quite probable. That's not ideal. However, it's not much of a problem, thanks to the lake precipitation effect. If there is any prevailing wind over the lake, it will cause some precipitation to occur nearby, and that should result in at least some small rivers, which our nice basin will cause to flow back into the lake. Here's an example of a lake that's affecting the local climate:

And here's the river map, showing the rivers in that patch of milder climate:

I rather like this, as the lake has its own complete, self-contained hydrological system going on there.

Of course, if the lake is somewhere without any prevailing winds (such as the horse latitudes, which are associated with deserts after all), this precipitation effect won't occur and so these rivers won't form either. But that doesn't happen tremendously often, and I don't think it's a huge problem - after all, desert rivers are pretty unreliable. Perhaps endorheic lakes with no inflows at all are just on the verge of drying up.

Now all of this raises the question: what about freshwater lakes? Could I adapt this basin-carving method to those, so that they too have wide drainage areas with rivers flowing naturally into them, avoiding all the messing about with river redirection that they currently involve? It's not as straightforward as it might seem. This new method of doing the salt lakes involves placing them at a low elevation - lower than the surrounding terrain - and then carving that surrounding terrain into a saucerlike depression around the lakes. But freshwater lakes need to have outflows. That means they can't be lower than all of the surrounding terrain. They must, in fact, be at the elevation of the outflowing river, which itself must then drop in elevation as it moves away from the lake.

Suppose we try the following. After the precipitation calculations, but before the river calculations, we pick random spots that aren't in deserts (these will certainly have rivers running through them of some size or another). We then use a lake template to flatten an area around the starting spot, such that any cells that are higher are lowered to be the same elevation as the starting spot, but any cells that are lower are left alone. Because of Planchon-Darboux, every area on the map has at least a gentle slope, so that should effectively mean that our starting spot now has a flattened area cut into the hillside above it. Then we run our basin routine around this spot. This again will affect the area upslope of our starting point. Then we run Planchon-Darboux again (well, we're already running it after creating the endorheic basins, so we'll just do the freshwater basins before that point so it captures them too). This should result in the flattened area becoming a gentle slope. Now, when we do the river calculations, any rivers within the catchment area of the basin should hopefully run down into it, meet, and flow out naturally as a single river from the starting spot down the slope that was already there. Finally, we place our lake over the flattened area in the normal way.

That gets us lakes like this:

It hasn't really worked. The lake has multiple outflows and its catchment area doesn't seem to be very wide. If the original selected point was on only a relatively shallow slope, then the basin carving won't have had much effect, and there might be multiple lake cells that are low enough to have rivers flowing out of them.

So we need to tinker further with the routine. What if we change it so that the basin is carved not merely by lowering cells that are too high, but by also raising cells that are too low? In other words, we don't simply press a giant saucer down on the terrain - we build one up on top of it. That way, we can guarantee a wide catchment area and also avoid multiple outflows. But we still want one outflowing river: how will that escape from the lake? Simple: we create an array and mark on that array all the cells downstream of our starting lake cell. Then, when we create the basin, we avoid messing with any cells that are marked on that array. That should ensure a path for one - and only one - river to leave the lake.

Trying that, I get the following world map:

Looks like I need to add a line stopping the basins from appearing over the sea. Also, of course, the basins rise up gradually as they get further from the central lake - and then they stop, leaving a sharp cliff edge down, so we have these enormous disc-like structures all over the place, rather like Venusian pancake domes. And, last of all, there's something wrong with the slopes of the basins - they're not directing the rivers towards the lakes:

So that was pretty comprehensively wrong. Let's try correcting some of these problems. Instructing the program not to create basins over seas is easy. Getting the slopes to work properly is harder. I tried increasing the steepness of the slopes, but that didn't help:

The whole basin is getting raised more than it should - you can see that the land is uniformly high right up to the shores of the lakes. I think the problem is the depression-filling algorithm. Marking out the path of the outflowing river doesn't seem to be stopping the algorithm from filling the basin up as if it's an unwanted depression. I try making that path wider, but it doesn't help.

After some time, my stupidity becomes apparent. All of this is being done before the flow directions are calculated. But when I mark the cells downstream of the outflow, I'm relying on the flow directions. So that isn't working. So when the basins are created, they don't leave any route for an outflow at all. So the depression-filling routine just fills the basins up completely.

So! I write a new routine to mark the downstream cells that doesn't rely on pre-calculated flow directions, but works them out on the hop. Now we have lakes like this:

You can see the raised basin there quite clearly - the edges are still sharp, so I will need to do something about that. The river map of this region shows the lake's catchment area even more clearly:

But by some miracle it does seem to be working. All the rivers in that area are flowing straight into our lake, except for the one that's flowing out of it, to the north.

I find similar patterns around other lakes. However, some have more than one outflow, which shouldn't be happening. I wonder whether this is somehow caused by the variations in the basin-creating routine. I change it so that the basins are smooth, and move the freshwater lake basin routine to be after the depression-filling routine associated with the salt lake basins. This doesn't help much - I'm still getting lakes like this one (which admittedly looks pretty cool, but surely isn't realistic):

There are also issues like this:

Look at all those rivers running to the lake through the mountains. The basin is created in such a way that mountains are basically ignored (remember, rivers are calculated on the basis of the underlying elevation, not the mountains themselves), and so we get unrealistic results like that. There are also some strange and annoying artefacts here: the outflowing river from this lake has turned into an estuary all the way along, and there are some really odd sections of sea disconnected from the main ocean, near the coasts. I'm not sure why. Parts of the coastline itself look too straight and blocky. I think that this last issue is caused by the high ground of the basin being near the coast, which forces the diamond-square routine to create less variation when working out the coastlines.

My conclusion: trying to do freshwater lakes using the basins method is just more trouble than it's worth. The existing method does more or less work most of the time, so I'll stick with that, at least for now. Which means that the second half of this post was basically a waste of time... but at least I gave it a try!