feat: generate the world map on the server - #13
Closed
JJiang28 wants to merge 1 commit into
Closed
Conversation
The world was a featureless 75x75 grid — no terrain, no biomes, no tiles table. Every tile the client drew was grass or fog, and settlements were scattered by Poisson-disk sampling with no idea what the ground underneath them was. Add internal/world: a dependency-free generator producing three orthogonal planes (ground, relief, vegetation) plus rivers and special resources, deterministic from constants.WorldSeed. Terrain is regenerated on every boot rather than persisted, so the map survives the reset that wipes everything else. MapService.GetTerrain ships the planes packed one byte per tile — a few kilobytes for the whole map, so it is neither paged nor filtered by vision. Three decisions worth recording: Planes stay orthogonal rather than collapsing into one biome enum. Collapsed, forest-on-tundra and forest-on-plains become separate values each needing their own art; kept apart, a feature composites over any ground. Thresholds are quantiles of the elevation field rather than fixed cutoffs, so retuning the noise cannot accidentally flood or drown the world — land is exactly landFraction whatever the octaves do. Noise is sampled at hexPoint(), not at (col, row). Columns sit 1.5 apart and rows only ~0.866, so sampling in grid space stretches every coastline and mountain range by about 1.73x horizontally once drawn. Placement is now terrain-aware. canPlace rejects footprints on water, peaks and ice, which halved the towns surviving seeding, so townMinSpacing drops 5 -> 4 to hold density roughly where it was. FindEmptyCityBlock no longer draws random empty blocks. Towns are seeded first and take the good land, so whatever the query leaves free is disproportionately the water and mountain the seeder rejected — players drawn that way reliably land inside a mountain range. It now scans the map in memory, scores every candidate on fertility, fresh water and coastline, and picks among the best 15% so consecutive registrations do not share a tile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/world, a dependency-free procedural generator producing three orthogonal planes (ground, relief, vegetation) plus rivers and special resources, deterministic fromconstants.WorldSeed. The world was previously a featureless 75×75 grid with no terrain of any kind.MapService.GetTerrain, packed one byte per tile — a few KB for the whole map, so it is neither paged nor filtered by vision.canPlacenow rejects footprints on water, peaks and ice, and player starts are chosen by scanning and scoring the map rather than drawing random empty blocks.Notes for reviewers
Two decisions that are load-bearing and easy to undo by accident:
landFractionregardless of what the octaves do, so retuning noise can't silently flood or drown the world.hexPoint(), not at(col, row). Columns sit 1.5 apart and rows only ~0.866, so grid-space sampling stretches every coastline and mountain range ~1.73× horizontally once drawn.FindEmptyCityBlockwas rewritten rather than tweaked. Towns are seeded first and take the good land, so whatever the SQL query leaves free is disproportionately the water and mountain the seeder just rejected — drawing from it seated the test player inside a mountain range. It now scans in memory and scores candidates on fertility, fresh water and coastline.Terrain-aware rejection halved the towns surviving seeding, so
townMinSpacingdrops 5 → 4 to hold density roughly where it was. That is a gameplay-density change, not just a visual one.Test plan
go build ./...,go vet ./...andgofmt -l internal/ cmd/cleanbuf lintclean;buf generatereproduces the committed output exactlymainafter feat: troops & armies (existence phase, no combat) #12 (troops & armies); both coexist — army actors spawn and terrain generates in the same boot