Make terrain steepness independent of map size #159

Open
opened 2026-10-11 20:23:45 +01:00 by liamjd · 0 comments
Owner

Milestone 2, item 3 (first half: "heights too rigid").

Problem

_sample_levels() normalises the raw noise over the window it sampled: it finds raw_min/raw_max across the grid and stretches that to −1..1. So every map, whatever its size, spans the full datum ± height_scale, which is 15 levels either way at the defaults.

A bigger map samples more of the noise field, so it is a gentler landscape; a small one gets the same climb crammed into fewer tiles. Measured over 10 seeds:

preset size flat 3×3 rocket sites (min / avg) share of land tiles that are flat
plains 16 0 / 3 2.6 %
plains 32 30 / 62 8.2 %
plains 64 364 / 542 14.9 %
plains 128 2038 / 2821 18.2 %

Same preset, same parameters, roughly 7× the flat land at 128 as at 16. #158 makes map size a player choice, which turns this from a curiosity into a balance problem: TINY would be the hardest map to build on.

Proposal

Treat a small map as a crop of a landscape, not a squashed copy of a large one. Per-tile steepness should be a property of the preset, not of the grid size.

  • Normalise by a fixed expected range of fractal Perlin, a constant measured once, rather than the sampled min/max. Clamp whatever exceeds it.
  • noise_frequency is already in per-tile units, so with the fixed normalisation a hill is the same size in tiles on every map.
  • The shape profiles (_shape_crater, _shape_canyon, the island falloff) are expressed in _radial() fractions of the map, so they do shrink with the map: a crater rim climbs rim_height over rim_radius × grid_size/2 tiles. Decide per shape whether that is right. A crater on a TINY map is probably fine as a steeper crater, but the canyon's canyon_width as a fraction of the map gives a 3-tile canyon at 32. Record the decision in the code either way.

Measurement

Commit a tools/probe_terrain_stats.gd (the measurement above was taken with a scratch version). For each preset × size × N seeds it prints: walkable share, main-region share, flat 3×3 site count (min/avg, zero-site seeds), level range, and flat-tile share. A headless -s script is fine: TerrainGrid does not reach an autoload. The later milestone 2 tickets quote it for their before/after numbers.

Acceptance criteria

  • For each preset, the flat-tile share at 32 is within ~25 % of the share at 128. The probe numbers before and after go in the PR.
  • The level range of a map no longer depends on its size beyond what the shape profiles intend.
  • Seed determinism holds: test_terrain_seed.gd stays green.
  • A unit test pins the size-independence. A loose, statistical bound over a few seeds, in the spirit of #16's "shape sanity checks, not golden images".
  • Headed screenshots of each preset at NORMAL, so the look has not drifted from what was there.

Depends on

Nothing hard. Worth landing before #160, which tunes the distribution on top of this range.

Milestone 2, item 3 (first half: "heights too rigid"). ## Problem `_sample_levels()` normalises the raw noise **over the window it sampled**: it finds `raw_min`/`raw_max` across the grid and stretches that to −1..1. So every map, whatever its size, spans the full `datum ± height_scale`, which is 15 levels either way at the defaults. A bigger map samples more of the noise field, so it is a gentler landscape; a small one gets the same climb crammed into fewer tiles. Measured over 10 seeds: | preset | size | flat 3×3 rocket sites (min / avg) | share of land tiles that are flat | | --- | --- | --- | --- | | plains | 16 | 0 / 3 | 2.6 % | | plains | 32 | 30 / 62 | 8.2 % | | plains | 64 | 364 / 542 | 14.9 % | | plains | 128 | 2038 / 2821 | 18.2 % | Same preset, same parameters, roughly 7× the flat land at 128 as at 16. #158 makes map size a player choice, which turns this from a curiosity into a balance problem: TINY would be the hardest map to build on. ## Proposal Treat a small map as a **crop** of a landscape, not a squashed copy of a large one. Per-tile steepness should be a property of the preset, not of the grid size. - Normalise by a **fixed** expected range of fractal Perlin, a constant measured once, rather than the sampled min/max. Clamp whatever exceeds it. - `noise_frequency` is already in per-tile units, so with the fixed normalisation a hill is the same size in tiles on every map. - The shape profiles (`_shape_crater`, `_shape_canyon`, the island falloff) are expressed in `_radial()` fractions of the map, so they **do** shrink with the map: a crater rim climbs `rim_height` over `rim_radius × grid_size/2` tiles. Decide per shape whether that is right. A crater on a TINY map is probably fine as a steeper crater, but the canyon's `canyon_width` as a fraction of the map gives a 3-tile canyon at 32. Record the decision in the code either way. ## Measurement Commit a `tools/probe_terrain_stats.gd` (the measurement above was taken with a scratch version). For each preset × size × N seeds it prints: walkable share, main-region share, flat 3×3 site count (min/avg, zero-site seeds), level range, and flat-tile share. A headless `-s` script is fine: `TerrainGrid` does not reach an autoload. The later milestone 2 tickets quote it for their before/after numbers. ## Acceptance criteria - [ ] For each preset, the flat-tile share at 32 is within ~25 % of the share at 128. The probe numbers before and after go in the PR. - [ ] The level range of a map no longer depends on its size beyond what the shape profiles intend. - [ ] Seed determinism holds: `test_terrain_seed.gd` stays green. - [ ] A unit test pins the size-independence. A loose, statistical bound over a few seeds, in the spirit of #16's "shape sanity checks, not golden images". - [ ] Headed screenshots of each preset at NORMAL, so the look has not drifted from what was there. ## Depends on Nothing hard. Worth landing before #160, which tunes the distribution on top of this range.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
liamjd/UntitledColonyBuilder#159
No description provided.