Sprint 14 - Adam-Poppenheimer/Civ-Clone GitHub Wiki

Goals

  1. Sensible estuaries for rivers when they make contact with standing water.
  2. Sensible behavior for river confluences.
  3. Sensible limitations on river flow and placement.
  4. The notion of fresh water, provided by lakes and rivers.
  5. Regression tests and sensible architecture for the HexMap namespace.
  6. Better trees with more sensible distribution.

Risks and Mitigation

  1. Going back and redesigning HexGridChunk could end up breaking that module, which could cause more hours of painstaking and fiddly work trying to get everything working again.
    • Whether I break HexGridChunk depends on the sorts of changes I make. If most of my changes are renaming operations, code separation, and the usage of interfaces rather than implementations, then there shouldn't be a lot of potential to break geometry. At the very least I can start with those changes and see how they flesh out, moving onto more complex changes only once I've resolved the easier ones. That way, even if I completely destroy my code I can always revert and walk away with something useful. Breaking my work into stages will be important here.
  2. In the past, I've wasted enormous amounts of time re-architecting code for no reason. If I'm not careful, I might fall into that rabbit hole again and waste hours doing nothing of any consequence.
    • I can mitigate this problem with task prioritization. I can start by working on the known big culprits: HexGridChunk and RiverTriangulator. Re-architecting, commenting, and streamlining these classes can be a task of Critical priority, while other modules can be Important or even Desirable, placed behind other non-architecture tasks. I can also continue to defer redesigns of classes until I need to modify them. This might be especially relevant to helper classes like HexCellShaderData. While that class lacks tests, that'll be fine as long as I don't need to change it and no bugs appear.
  3. While I do see value in some sort of unit testing or regression testing for HexGridChunk and RiverTriangulator, it's not clear to me what tests have value and what tests don't. That might make it hard to create productive test fixtures.
    • I can think back to the struggles I was just experiencing. One of the more vexing challenges I had was figuring out where my case selection code was working. Whether a particular case was necessary, possible, impossible, or executed by a particular set of cells was often unclear, to the point where I'd spend an hour staring at the wrong code. Knowing exactly how these classes call into HexGridMeshBuilder might also be useful, since that's the primary side-effect of both these classes. I can create smaller helper classes (perhaps that take into account one or two cases) and make RiverTriangulator's primary responsibility properly calling methods in these helper classes. I can then decide on a case-by-case basis whether building tests for those helper classes is worth it.
  4. Though I can't be certain with my level of expertise, I think it'll be impossible to generate estuary effects with a single triangle. And I know it's impossible to form sensible river flow for my confluences with a single triangle using any methods I know of. Especially for confluences, that makes it unclear how I'll get a reasonable effect.
    • I can handle estuaries by stealing some triangles from the adjacent edges. I should be able to get a trapezoidal- shaped collection of triangles similar to what the original code used for non-edge rivers. Confluences might take an entirely new approach, perhaps similar to estuaries. For them, I can try creating two triangles for the non-waterfall case. That might look acceptable if the math works out. Waterfalls will add more complexity, since I can't easily match the flow of the waterfall to the flow of the other rivers. I might be able to solve this with particle effects, maybe creating a cloud of mist around the confluence. Or I could create a generically noisy water pattern that merges into the rivers. At the very least, I can modify the color of the confluence triangle to more closely match the color of the river. That'll help make it less noticeable that the rivers don't flow properly.
  5. It's not clear what classes in HexMap should receive test fixtures. Surely RiverTriangulator and HexGridChunk need attention. But does FeatureManager? How about HexCellShaderData? More tests will give me more confidence, but could also waste time.
    • I can apply test fixtures as I apply refactors, and tie them to the same prioritization. That way I'm always concerning myself with the most important tests on the most important classes that are in need of attention.
  6. Right now, elevation perturbations can cause river surfaces to appear above the surface of the water. This is fine when rivers are draining into open water, but much less sensible when those rivers are being fed from it. Synchronizing these elevations, plus differing elevations between different river segments, in the general case could lead to a bulky if-then nightmare that's hard to think about and maintain.
    • There are a few ways I could solve this. Reducing or eliminating the perturbation would make it less noticeable. I could also set a river elevation that's standard across all cells. Doing so would eliminate waterfalls and cause problems with hills and mountains, but those could be resolved by reducing the height of such features. Or I could have two possible elevations for rivers, which would preserve waterfalls while also making things a lot simpler. I could add additional conditions to RiverCanon and forbid any river that would flow substantially uphill. Small uphill slopes aren't noticeable most of the time, so it's possible I could ignore everything but waterfalls in most cases, especially if I'm willing to fiddle around with my config.I could also force rivers to only flow out of Lakes and increase their water level so that river surfaces will lie at or below the level of their sources.

Review

Most of this sprint was spent focusing on visual improvements and organizing the HexMap namespace.

For organization, I've separated my triangulation code into a number of sensible pieces, added some unit tests to check for at least some possible errors, and have resolved some bugs in the triangulation. I've also made my triangulation code more consistent, so that all classes triangulate the previous corner. It's not clear that I spent my time productively on these tasks.

From a visual improvement standpoint, I completed road triangulation. Roads now handle hills properly, so that roads now appear over every cell type that can take them. The only thing I'm missing from Civ 5 is bridges, which isn't implemented mechanically either. I've added visual representation of jungles, so that players can now see jungles and distinguish them from forests. I've established a bunch of new restrictions on river placement to make invalid river flow less common.

I made a number of changes to how the map is painted, which has had mixed results. It's a lot harder to paint invalid shape/terrain/feature combinations, or to accidentally paint multiple types of features at once. I attempted to improve river drawing as well, though that system is still fiddly and unintuitive.

I modified FeatureManager so that it adds a much greater number of much smaller features, and also handles city, resource, tree, and improvement features much better. This has led to severe performance problems when large numbers of features are present, which I'll have to address in a future sprint.

I've finally come up with a decent solution for river confluences, as well. Rivers now join and split in sensible ways. I forgot to check to see if waterfalls look sensible, which I'll definitely have to deal with in a future sprint.

I ended up performing a lot less unit testing than I'd expected, and generally struggled with testing my triangulation code. It remains unclear what code coverage would be useful here and what would be unproductive. I suspect I'll continue to grapple with that in the coming weeks.

Retrospective

What went well?

  1. I came up with a pretty reasonable solution for river confluences, and did so by reasoning mathematically about the problem rather than taking wild guesses and stumbling upon a proper solution. And I managed to fit behavior on a single triangle.
  2. My triangulation code is now so much easier to look at and reason about. The fact that everything is separated into individual classes that have clear responsibilities means it's much easier to find the code that performs a particular activity. And the fact that things are organized and consistent makes it easier to reason about what's going on.
  3. Roads ended up being surprisingly simple. I had expected roads to give me a lot of problems, but it turns out I understand the triangulation code well enough that I could apply the logic of roads to my modified use cases without much trouble.
  4. I've substantially improved the way features are drawn and how that whole system is organized, which makes the map look a lot better. The game's scale now resembles Civ 5's scale a lot more closely.

What could be improved?

  1. It's not entirely clear to me how useful HexGridMeshBuilder is as a class. Many of its methods have large enough argument lists that they don't reduce the line count of code that uses them. I worry that this class might obscure what a particular method is trying to accomplish. However, I also don't think it's worthwhile to go back and redo that section of the code. I should remain skeptical of that class but resist the urge to change it.
  2. River drawing is still an unintuitive and unpleasant experience. I need to take another crack at that, since I know fiddly river drawing is going to cause me a lot of problems and a lot of frustration in the future.
  3. The feature system is now causing pretty severe performance problems that'll need to be addressed in any final product. The sheer number of features is causing a lot of render calls. I'll need to use some combination of chunk unloading, graphics optimization, camera placement, and any other techniques I can find or come up with to address this now-major issue.
  4. It seems as if my map saving and loading system isn't backwards compatible, despite backwards compatibility being a design goal. I need to spend some time figuring out how to deal with older versions of saved games. At the very least, I need to make sure that older files are ignored, are marked as obsolete, or at least don't throw exceptions when they're selected.
  5. I still don't have a solution for displaying marshes. Given that I'm focusing on visual aspects of the game, I should probably spend some time figuring out a triangulation solution for marshes.