Problem
The terrain-wonders wave (#698–#709, PR #710) made WorldGenerator.SurfaceHeight ~2.6x more expensive: the PR CI test job went from ~6.5 min to ~17 min, and the same cost hits the game itself (chunk generation, the ~17k-sample per-world calibration sweep, river routing, previews, settlement placement).
Root causes (all per-COLUMN work that is constant per WORLD)
- The landmark chain re-evaluates ~10 feature gates (
HasVolcanoes, HasCalderas, HasTravertine, HasCenotes, HasMegaRift, …) for every column — including Noise.Hash world rolls and field comparisons whose answers never change for a given world.
- Several gates do string work per column:
TerrainStyle.ToLowerInvariant() (allocates!), string.Equals(..., OrdinalIgnoreCase) on planet keys, StyleHybridEligible switch on a lowered string. Millions of short-lived string allocations → GC pressure.
PlanetSeed re-hashes the planet-key string on every call; SurfaceHeight + RawSurfaceHeight + GetExtraBands + TunnelSpans each call it again per column.
GrainFor re-rolls the per-world terrain grain from Noise.Hash per column; the continent profile and hybrid thresholds are similarly recomputed.
Fix
Introduce a per-world wonder profile cached like CalibFor (static dictionary keyed on seed/planet/circumference/cratered/salt/continents): planet seed, terrain grain, hybrid thresholds, continent profile, and one boolean per feature family. All hot-path callers read the profile; per-column code becomes arithmetic only.
Acceptance
Problem
The terrain-wonders wave (#698–#709, PR #710) made
WorldGenerator.SurfaceHeight~2.6x more expensive: the PR CI test job went from ~6.5 min to ~17 min, and the same cost hits the game itself (chunk generation, the ~17k-sample per-world calibration sweep, river routing, previews, settlement placement).Root causes (all per-COLUMN work that is constant per WORLD)
HasVolcanoes,HasCalderas,HasTravertine,HasCenotes,HasMegaRift, …) for every column — includingNoise.Hashworld rolls and field comparisons whose answers never change for a given world.TerrainStyle.ToLowerInvariant()(allocates!),string.Equals(..., OrdinalIgnoreCase)on planet keys,StyleHybridEligibleswitch on a lowered string. Millions of short-lived string allocations → GC pressure.PlanetSeedre-hashes the planet-key string on every call;SurfaceHeight+RawSurfaceHeight+GetExtraBands+TunnelSpanseach call it again per column.GrainForre-rolls the per-world terrain grain fromNoise.Hashper column; the continent profile and hybrid thresholds are similarly recomputed.Fix
Introduce a per-world wonder profile cached like
CalibFor(static dictionary keyed on seed/planet/circumference/cratered/salt/continents): planet seed, terrain grain, hybrid thresholds, continent profile, and one boolean per feature family. All hot-path callers read the profile; per-column code becomes arithmetic only.Acceptance
SurfaceHeightcomparison over sampled worlds of every planet type (plus chunk-hash spot checks) — this is a pure refactor, no behavioral change.