islaApocalypse/Tools/Scripts
beezm d296ef30c4 feat: shared water predicates + water-bodies stage + priority-flood diagnostics (terrain-water task 03)
IsWaterPixel/IsOceanPixel/IsLakePixel are the single per-pixel water
truth; AssignBiomesAndDraw's water branch now calls them (verbatim
rules — behavior-preserving, proven by the bit-identical-biomes oracle
at acceptance). New IdentifyWaterBodies stage between the masks and
biomes: ocean = body 1, lakes labeled 2..N with CalculateTrueOcean's
4-connectivity in deterministic scan order; one transitional surface
level per body (GetSeaLevel at the body centroid; ocean at map
centre). 0_water snapshot painted from the stage's own outputs.

Priority-flood pit-fill (heap + pit-queue variant) runs as validated
diagnostics only — serializes nothing, asserts filled>=original and
full-map non-ascending drainage (reverse BFS, no sampling), reports
closed-basin statistics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 01:29:51 -04:00
..
MapGenerator.cs feat: shared water predicates + water-bodies stage + priority-flood diagnostics (terrain-water task 03) 2026-08-07 01:29:51 -04:00
MapGenerator.cs.uid baseline: working salvaged prototype on Godot 4.7.1 (pre-F1) 2026-08-05 01:45:41 -04:00
MapGenerator.md docs: bring all READMEs current with actual code (post-sweep-10 truth pass) 2026-08-05 05:23:58 -04:00
README.md docs: BLUEPRINT_FORMAT.md + README updates for the v2 container (terrain-water task 02) 2026-08-06 07:31:03 -04:00
RoundTripHarness.cs feat: blueprint v2 envelope — writer, dual-write, round-trip harness (terrain-water task 02) 2026-08-06 07:24:08 -04:00
RoundTripHarness.cs.uid chore: Godot .uid files for the new task-02 scripts 2026-08-07 00:17:46 -04:00

/Tools/Scripts — generation logic

C# backend for the offline developer tools. Decoupled from the live server and client.

MapGenerator.cs

Generates the entire 2D blueprint. Roughly in order:

  1. Config — reads ServerConfig.json, which sets MapSize (8192 by default) and the crater radius. The MapSize = 4096 field initialiser is a fallback that is immediately overwritten.
  2. Topography — FastNoiseLite base height plus a mountain spine, minus a squircle distance falloff, giving a guaranteed island. Noise frequency is divided by scaleFactor (MapSize / 1024f) so terrain features stay the same real-world size at any map profile.
  3. Sea level and water — temperature-driven sea level; flood fill separates true ocean from inland lakes; a mainland fill guarantees one contiguous landmass.
  4. The crater — placed along the northern coast and carved to below sea level, but only out to 80 % of its radius, which guarantees a landbridge rather than severing the island.
  5. Biomes and towns — biome zoning by height and temperature; tiered town placement (Capitol, Hubs, Villages, Outposts, POIs) filtered by slope, water proximity and spacing.
  6. Roads — see below.
  7. Export — writes the .dat blueprint (dual-write: the v2 tagged container under the primary seed name, plus the legacy v1 format beside it as _v1.dat — see Core/Scripts/BLUEPRINT_FORMAT.md), then renders the PNG snapshot. The v2 file embeds the resolved generation params (seed, MapSize, crater radius, density, impact centre, provenance).

The road network

AStarGrid2D over the whole map at one node per pixel. Mountains are made expensive rather than impassable (1 + elevation³ × 400), water and the crater are marked solid.

  • Continental loop — highway nodes sorted by radial angle and connected in a ring.
  • Mountain branch — a spur from the loop to the snow hub.
  • County roads — Prim's algorithm daisy-chains remaining towns onto the network. Villages become Rugged roads, everything else Trails.
  • Abandon protocol — a town further than 8 % of the map from the network is skipped rather than pathed to, so one unreachable outpost cannot hang generation.
  • Smoothing — every path is decimated with RamerDouglasPeucker (tolerance 4.0) then smoothed with 4 Chaikin passes.

This is the project's dominant performance problem. At 8K the grid is 67 million nodes, and the weight-setup pass touches every one before the first path is requested. The await yields between stages cannot interrupt a single engine-side GetPointPath call, so a long path still blocks. Full regenerations frequently get abandoned.

The PNG snapshot

SaveMapSnapshot() builds an offscreen SubViewport in code rather than relying on the scene's layout, because Godot's UI layout engine will otherwise crush a 4096+ render down to the editor window size. A MapDrawProxy control re-issues the _Draw() calls into that viewport — GetImage() captures only the base texture and would otherwise miss the roads and towns entirely — and the code awaits two RenderingServer.FramePostDraw signals so the GPU has actually painted before the image is read back.

Road colours on the snapshot, useful for identifying a road: red = Highway, black = Branch, dark brown = Rugged, light brown = Trail.

Outputs

MapData_Seed_<seed>.dat (v2), MapData_Seed_<seed>_v1.dat (legacy dual-write), and Map_Seed_<seed>.png, all to user://.

RoundTripHarness.cs

The blueprint format regression test (scene: Tools/Scenes/RoundTripHarness.tscn). Loads a known-good blueprint through the real parser, re-writes it as v2 through the real writer, re-loads it, and asserts semantic equality (heights bitwise, biomes, towns, every road point). Headless, seconds per cycle, no generation. Exit code 0 = pass.

Rules

  1. No magic numbers. Distances, radii and thresholds derive from MapSize or scaleFactor, so the generator behaves identically at 4K and 10K.
  2. Respect the GPU. Anything exporting visual data must await the appropriate frame signals before reading pixels back.