Settlements, paths and vegetation on generated terrain, and drawing height in 3/4 oblique pixel art
Notes compiled 2026-11-04 for Elsewhere and the Tycho engine report. Target: plain JavaScript on a phone, tile grids of roughly 40x25 to 256x256 cells, deterministic from a seed, painted into palette-indexed buffers. Sibling notes cover overlapping ground: world_generation.md (Watabou, Parish & Müller, Bridson, autotiling), plants_trees_flowers.md (drawing the plants themselves) and styles_resolution_perspective.md (projections). This file covers where things go and how height reads.
Note on sourcing: the three Galin/Emilien papers, Deussen 1998, Cordonnier 2017, Gain 2017 and Bridson 2007 were downloaded and read in full text (PDF to text with mutool). The Merryfolk game was read from local source. Other items come from fetched web pages or, where marked “(search summary)”, from search-result summaries only; treat those as less certain.
Settlement siting: what real people weighed
Takeaway
Historical siting is a small set of local “site” factors (a reliable water source, flood-free dry ground, sun-facing and sheltered aspect, gentle arable slope, defensibility) plus “situation” factors (fords, bridging points, junctions). The best single heuristics for a generator are the spring line (settle at the foot of a slope where water emerges, above the floodplain) and the adret rule (settle and farm the sun-facing slope, leave the shaded slope wooded).
Cited Findings
- Spring line. Spring line settlements form where “a ridge of permeable rock lies over impermeable rock,” so springs emerge along the contact between the layers. Building higher risked water shortage; building lower moved the village away from grazing land or onto the floodplain. Such villages form a linear chain along the foot of an escarpment and anchor strip parishes, “long, narrow parish boundaries stretching up to the top of the ridge and down to the river.” Examples: the South Downs (Edburton, Fulking, Poynings), the Mendips (Wells, Wookey Hole, Draycott), the Lincoln Edge (Navenby, Metheringham). The article rests on one source, and several examples are marked “citation needed.” — Wikipedia: Spring line settlement
- Wet point and dry point. Wet-point sites are chosen for access to water. Dry-point sites are firm, flood-free ground within wetland, marsh or floodplain (Glastonbury is the usual example). Other standard site factors are defense, nodal points (route junctions) and bridging points. These are school-level geography texts (search summary). — Rashid Faridi: Site of a Settlement; Save My Exams IGCSE flashcards
- Aspect and shelter. Aspect and shelter are described as “two of the most important factors” in siting, with the spring-line villages under the Downs’ chalk escarpment as the example of shelter (search summary). — British Landscape: Reading the Landscape
- Adret and ubac. The adret is the sun-facing slope, south-facing in the Northern Hemisphere, a term used mostly of the Alps. On the ubac (shaded slope) the timber line and snow line sit lower than on the adret. — AMS Glossary: adret; AMS Glossary: ubac
- A French teaching source summarizes the land use: adret “warm and dry => clearing, village, livestock, crops”; ubac “cold and damp (snow earlier and later) => forest, then winter sports resorts” (search summary). The Écrins national park material says villagers settled the sunniest zones in winter. — cahier-de-prepa.fr course notes; Parc national des Écrins booklet
- Emilien et al. 2012 state the same premise for generators: “most human settlements did not result from some pre-defined land-use plan, but from people progressively settling in safe, well served, sunny or convenient location for farming or fishing. Meanwhile, the road networks progressively grew and in turn attracted new settlers,” citing Barry (ed.), A Story of Settlement in Ireland (Routledge 1999). — Emilien et al. 2012 PDF
- Treeline as an upper limit for farming and wood. The climatic treeline sits where the growing-season mean root-zone temperature is about 6.4 ± 0.7 °C (Körner 2012; Paulsen & Körner 2014, TREELIM model: days with daily mean above 0.9 °C, season of at least 94 days). Körner & Paulsen 2004 first reported 6.7 ± 0.8 °C from 46 sites between 68° N and 42° S (search summary). — GMBA treeline definition; Körner & Paulsen 2004, edoc Basel; PMC3535059
- Temperature lapse rate used by Cordonnier et al. for altitude: 6.5 °C per 1000 m, θ(p) = θ̄ − 0.0065·elevation. — Cordonnier et al. 2017 PDF
Inferences
- A practical real-world site score for one cell, built from the factors above (weights are a starting point to tune, not sourced):
water: 1 at distance 1 to 3 cells from a spring, stream or lake, falling to 0 by about 8 cells. Springs can be placed at the foot of slopes where height drops sharply and flow accumulation is moderate (a spring-line proxy).dry: 0 if the cell is in the floodplain (within about 1 to 2 height steps of the river and below a flood level), else 1. This gives the terrace-above-the-river preference.sun: 0.5 + 0.5·(the slope’s facing toward the sun, from -1 to 1), scaled by steepness. In a screen-up-is-north world, that means slopes falling toward the bottom of the screen.shelter: the fraction of the upwind half-circle within about 6 cells that is higher than the cell (a ridge or wood to windward).flat: a bell on slope that peaks at 0 to 5 percent and is 0 above about 25 percent.arable: the mean offlatand soil quality within about 5 cells.defense: the Emilien “geographical domination” sum (see the next section), used only for castles, churches and hill forts.woodandstone: distance to forest edge and to rock or cliff, as a soft bonus.route: a bonus at fords (shallow narrow crossings), confluences and junctions of least-cost paths.
- Score = Σ wᵢ·fᵢ with any hard veto (water cell, slope above the limit) forcing 0, exactly the Emilien combination rule below.
- “Site vs situation” maps onto two passes: score sites from local terrain first, then add a situation term once the inter-settlement path graph exists. Christaller-style spacing was not researched further. A minimum spacing between villages, as a Poisson-disk radius, gives the same visual effect at no cost.
Gaps
- I found no primary historical-geography text (for example, a university textbook) fetched in full. The site-factor list rests on Wikipedia and school-level summaries. They are consistent with each other and with Emilien et al.’s premise, but they are not primary sources.
- No quantitative data (for example, “villages are typically within N meters of water”) was found.
- Prevailing-wind shelter is asserted by the sources but not quantified.
Settlement siting: how games and generators score sites
Takeaway
Emilien et al. 2012 is the one fully documented scoring model, and it fits a phone: random candidate positions, each scored by weighted interest functions in [−1, 1] with a hard veto, then accepted with probability proportional to the score. New roads update the score maps, so houses and roads grow together. Watabou’s village generator puts the village at a high-density point on a highway, around a distorted circle that becomes the square. Dwarf Fortress and the city-builders document their siting only loosely.
Cited Findings
- Emilien et al. 2012, “Procedural Generation of Villages on Arbitrary Terrains” (The Visual Computer 28:809–818). This is a three-step method: (1) iterative growth of settlement seeds and roads from interest maps, (2) anisotropic land conquest into parcels, and (3) an “open shape grammar” for houses on slopes. — PDF; HAL
- Inputs: elevation h(p), water height w(p), vegetation density v(p); connection points Ψ on the region’s edge, where roads leave the map; a village type V (for example, highland, defensive, fisherman); a “building encyclopedia” returning parameters for each (V, building type B) pair; and a growth scenario, which is a timeline of events that either change V (for example, a war period followed by a farming period) or seed n buildings of type B. — PDF §3–4.1
- Seeding algorithm: pick a random position p and check constructibility (for example, not in a lake). If buildable, compute interest I(B), then run an “aggregation test,” a random accept with success probability depending on I(B). On failure, pick a new random p. This is adapted from the authors’ lichen-growth “Open Diffusion Limited Aggregation” model. — PDF §4.2
- Interest combination: n criteria fᵢ(B) ∈ [−1, 1] (−1 means impossible), with weights wᵢ per (V, B). I(B) = 0 if any fᵢ = −1; otherwise I(B) = max(0, Σ wᵢ·fᵢ(B)). — PDF §4.2
- The criteria: sociability, a sum of attraction-repulsion functions of distance to other buildings with λmin, λ0 (preferred distance) and λmax per pair of building types, so terraced houses cluster tighter than farms; worship, the same function computed only toward religious buildings; accessibility, a non-symmetric bell on distance to the nearest road, with preferred distance λ0 and construction prohibited outside [λmin, λmax]; slope, a bell around a preferred slope with a prohibited range, so “corn farms to flat areas and vineyards to hills”; water, a decreasing “close distance” function of distance to water between λmin and λmax; fortification, an “open distance” function equal to 1 inside walls and decreasing outside; geographical domination, f(x) = Σ over samples p with ‖x−p‖ < r of (h(x) − h(p)) / (1 + ‖x−p‖²). Sociability and accessibility are recomputed dynamically as buildings and roads are added. The others are static precomputed 2D maps. — PDF §4.2, Fig. 4
- Results: mountain hamlets were driven mainly by geographical domination; a cliff-top fortified village by domination plus fortification; a fisherman village by distance to the sea. — PDF §7
- Costs and limits: written in C++. Skeleton generation took 4 to 7 minutes, parcels 7 to 13 minutes on a 4096×4096 grid, and geometry 20 to 30 seconds. The main limitation is “the number of user-set parameters, currently 150 per village type.” — PDF §7, Table 1
- Watabou’s Village Generator. “The generator finds a place on a map with high population density - often it’s a place on the highway. Then it creates a circle of a reasonable size at that location and distorts it a little. This circle becomes the shape of a village square.” If the square doesn’t overlap water, a centerpiece (a large tree or a well) is added. — watabou devlog 1.4.0
- Version 1.6.4 added a “crossroads” tag to request a village “at a highway intersection (a crossroads or a T-junction).” The algorithm details are on Patreon and were not accessible. — watabou devlog 1.6.4
- Dwarf Fortress (wiki, partly for older versions; search summary): high savagery (66+) blocks civilization placement; the total number of new sites (towns, hamlets, forest retreats) is capped by a world parameter shared across civilizations; “hillocks” (dwarven hamlets) “are equally likely to be found in any land biome.” Worlds that fail placement constraints are rejected and regenerated, with reasons written to a rejection log. — DF wiki: World rejection; DF wiki: DF2014:Site; DF wiki: v0.31 Civilisation
- Merryfolk (local game) derives the land in Dwarf Fortress-like stages: height → slope and aspect → depression filling and drainage → wetness → soil. Its comment: “Worked out in stages, each reading the ones before (as Dwarf Fortress builds its worlds).” — /Users/david/projects/merryfolk/js/terrain.js
- Ostriv: the only material found was player discussion of long narrow house plots with road or waterfront access (Reddit) and a 2023 note that Alpha 5 added two- and three-story Ukrainian Baroque houses and paved roads. — Mezha: Ostriv Alpha 5
Inferences
- For a phone, Emilien’s loop is cheap if the grid is the game’s tile grid (for example, 64×64 = 4096 cells) rather than 4096²: one candidate costs a handful of map lookups plus a neighbor sum. Static maps (slope, water distance, domination) are precomputed once with BFS or a box filter. Dynamic maps (road distance, sociability) are updated incrementally: after each road, run a BFS from the new road cells only; after each house, add its attraction kernel into a sociability grid. Expect well under 50 ms for 30 to 80 houses in JS.
- The 150 parameters per village type are the method’s real cost. For Elsewhere, ship 3 to 4 village types with about 6 active criteria each (slope, water, road access, sociability, sun, domination), with weights in a small table per quest.
- Make the aggregation test deterministic with
ctx.rng('village'). Cap attempts (for example, 200 per house) and fall back to the best-scoring candidate seen, so generation always ends. - The geographical domination formula with r of about 6 to 10 cells works as a hill-top detector for churches, castles and lookout towers.
- Watabou’s “find a dense point on the highway, distort a circle, make it the square, add a well or tree” is a good seed for a round-the-green village. Emilien’s loop then grows houses with worship-like attraction toward the square instead of a church.
Gaps
- No primary documentation was found for site scoring in Banished, Foundation, Songs of Syx or Ostriv. These games let the player place buildings. I found no developer write-up of AI or generator siting rules.
- Dwarf Fortress’s current (v50) site-placement algorithm is not documented publicly beyond wiki observations. The DF2014:Site page returned HTTP 429 on fetch.
- Watabou’s house-placement rules are behind Patreon and were not accessible.
How a village grows along a road or round a green, and how buildings are oriented
Takeaway
Real and generated villages share three regularities: houses join the road network as they appear (and roads attract houses), parcels have one side on the road with two sides roughly perpendicular to it, and buildings face the nearest road. Emilien et al. encode all three and validate parcel statistics against a real village.
Cited Findings
- Emilien et al.: “a new road attracts settlers while a new house often leads to some extension of the road network.” Each new building seed is immediately connected to the network by a least-cost road, and the accessibility map is updated. — PDF abstract, §4.3
- Road reuse: the cost of a new road is C(R) = Σ wⱼ·gⱼ(R) (slope, curvature, water, and a high constant for crossing a building). Segments on an existing road cost w_ex·C(R) with w_ex ≪ 1, so new houses branch off existing roads instead of making “a fully ramified road network.” — PDF §4.3
- Road cycles: after the first road to a new building, the method looks for the closest road node in a cone of angle θ ahead along the road direction and builds a second road there, if it is not too close to the first, creating loops and shortcuts. — PDF §4.3
- Parcels: “most parcels have one side neighboring a road and two sides perpendicular to it.” The conquest runs in four steps. (1) Road conquest: from the building’s projection onto the closest road, spread along the road both ways until meeting a neighbor or reaching a maximum distance. (2) Corner conquest: the parcel that first reaches a junction annexes the corner, and seeds that lose road access are deleted. (3) Anisotropic grid conquest: each road cell is a source with a budget c_max, and the cost of conquering a cell is Σ ωᵢ·cᵢ(cell, n), where n is the road normal. (4) Simplification: drop vertices where the turn angle is below ε, and remove acute T-vertex angles. — PDF §5
-
Conquest cost terms: the distance to source is Euclidean for fields, and for houses it is the “infinite” distance d = max(p·n, p·t) , which gives near-quadrilateral lots; water, wall and road cells cost +∞; slope cost is “a quadratic function of the directional gradient,” so parcels spread along contours rather than downhill. — PDF §5.3 - Validation: generated parcels averaged 2.873 neighbors and 4.29 contour edges against 2.812 and 4.068 in the real village data. — PDF §7
- Footprints and orientation: a quad is initialized “at the closest position to the road, and oriented according to the closest normal to the road,” then grows until it reaches the building type’s maximum size or hits the parcel contour. Segments close to the contour snap to it, which produces terraced rows when neighbors do the same. — PDF §5.5
- Merryfolk keeps an analogous “settled” distance field (a BFS from every non-grass or reserved tile) and uses it to clear woods around the village (see the vegetation section). — /Users/david/projects/merryfolk/js/nature.js
- Player discussion of the village generator (not the author) splits between “villages sit on both sides of a trade road with perpendicular side roads” and “small villages should not have roads running through them” (search summary). — watabou village generator comments
Inferences
- A tile-grid recipe:
- Lay the through-road from one map edge to another (Emilien’s Ψ points) with least-cost A*.
- Pick the village center: the highest-scoring cell on the road, or a road junction (Watabou).
- For a green village, carve a distorted circle of radius 3 to 5 tiles as the green, with a well or tree at its center. For a street village, skip this step.
- Seed houses with the Emilien loop, using accessibility (λ0 of 1 to 2 tiles from the road), sociability (λ0 of 2 to 3 tiles) and slope.
- Connect each house with A* where road tiles cost w_ex = 0.1 to 0.2.
- Face each house toward its nearest road tile. In 3/4 view only four facings exist, so take the sign of the dominant axis of (road − house).
- Strip fields: from the road edge, grow lots perpendicular to it and stop at slope changes. Equivalently, extend the road normal until slope × distance exceeds a budget.
- Round-the-green houses should face the green: use the vector to the green’s center instead of to the road.
- On slopes, the parcel slope cost (quadratic in the directional gradient) means terraced rows follow contours. In 3/4 oblique this reads well as rows of houses stacked up a hill, each row on its own ledge.
Gaps
- No primary source was found on the historical morphology of linear (street) versus nucleated (green) villages beyond the spring-line article. Classic English village-morphology texts (for example, Brian K. Roberts) were not fetched.
- The rule that houses face south or face the road in various cultures was not researched.
Paths: desire paths, least-cost paths, roads, fords and bridges, switchbacks
Takeaway
Three families cover everything. (1) Least-cost A* over a cost grid with slope cost (Tobler for walkers; capped slope and curvature costs for roads), using larger neighbor masks to escape 45° artifacts. (2) Reinforcement models (Helbing’s active walker) where use lowers future cost and disuse decays it, which produces desire paths and trunk trails. (3) Network building: connect settlements with least-cost paths, prune with a proximity-graph rule, merge parallel roads, and make existing roads cheap.
Cited Findings
-
Tobler’s hiking function: W = 6·e^(−3.5· S + 0.05 ) km/h, with S = dh/dx = tan θ. That gives about 5.04 km/h on flat ground and a maximum of about 6 km/h at S = −0.05 (about −2.86°, slightly downhill). Pace form: p = 0.6·e^(3.5· S + 0.05 ) s/m (about 0.71 s/m flat). Off-path travel: multiply speed by 3/5; horseback: by 5/4. It was fitted to Eduard Imhof’s data (Tobler 1993). — Wikipedia: Tobler’s hiking function - The R package movecost implements least-cost paths with Tobler and other cost functions cell by cell. A USDA Forest Service study found a single function misses travel-rate variability (search summary). — movecost vignette; par.nsf.gov 10420114
- Galin et al. 2010, “Procedural Generation of Roads” (Computer Graphics Forum 29(2), Eurographics). — PDF
- The road minimizes the line integral of c(p, ṗ, p̈) = Σ μᵢ∘κᵢ(p, ṗ, p̈). The characteristics κ are vegetation density v(p) (the number of trees within radius r), water depth w(p) (the maximum within radius r), slope along the direction of travel s(p, ṗ), and curvature. The μ are user-drawn transfer curves with a threshold κ0 above which the cost is +∞ (no road on slopes steeper than κ0, none through water deeper than κ0; shallow water is allowed “at the expense of some banking”, which is a ford). — PDF §4.1
- Bridges cost a function of bridge height above ground h(p) (+∞ above the type’s maximum), slope, the depth of water crossed, and curvature. Tunnels cost a function of depth below the surface, slope, and the depth of water above. Both are evaluated as long straight “path segments” between distant grid points, using special tunnel and bridge masks with a minimum and maximum length per type. — PDF §4.2, §5.3
- Path segment masks M_k: neighbors (i, j) ∈ [−k, k]² with gcd(i, j) = 1. Angular resolution α and neighbor count n_k: k=1: 45°, 8; k=2: 26.5°, 16; k=3: 18.5°, 32; k=4: 14.0°, 48; k=5: 11.3°, 80; k=6: 9.5°, 96. On a 60×60 grid (2009 hardware), k=1 took 0.015 s with a path length of 477 m and an “unrealistic direct path to the top,” while k=4 took 0.110 s with a length of 1757 m, a switchbacking path. Time grows O(k²), and “k = 5 proved to be a good compromise.” — PDF §5.1, Tables 1–2
- Curvature: without it, paths have “too many sharp turns.” The fix is a 3D search over (x, y, heading), with headings discretized into m bins. With m = 12 on 60×60, time rose from 0.110 s to 1.750 s. — PDF §5.2, Table 3
- The search is A* with a heuristic equal to the cost of a straight road to the goal. — PDF §3
- Galin et al. 2011, “Authoring Hierarchical Road Networks” (Computer Graphics Forum 29(7), Pacific Graphics). — PDF
- Settlement sizes are σ ∈ {city 2, town 1, village 0}, and a road’s type is τᵢⱼ = min(σᵢ, σⱼ): highways connect cities, major roads connect cities and towns, minor roads connect towns and villages. Generation runs top-down by level.
- At each level: compute least-cost paths for the complete graph between nodes, keep a subset by a non-Euclidean proximity rule, then merge near-parallel paths into junctions (Steiner nodes). The proximity region is Ω_γ(a, b) = {p : d(a,b)^γ < d(a,p)^γ + d(b,p)^γ}, where d is least-cost distance. Keep edge (a, b) only if no other node lies in Ω_γ. γ = 2 gives a cost-metric Gabriel graph; γ → ∞ gives the relative neighborhood graph; lowering γ adds edges.
- A “control cost” h(p) is added to c: positive near already-generated roads (with a decreasing radius per level) to limit redundant parallel roads and junctions, or painted by the designer to keep roads out of parks or cities. Persistent waypoints force a road through a point.
- Helbing et al. active walker model (Helbing, Schweitzer, Keltsch & Molnár; Phys. Rev. E 56, 2527, 1997). — arXiv cond-mat/9806097; arXiv cond-mat/9805158
- Ground: dG/dt = [G⁰(r) − G(r,t)]/T(r) + Σ_α I(r)·[1 − G/G_max]·δ(r − r_α). That is, trails relax back to bare ground G⁰ with durability T, and each footstep adds intensity I, saturating at G_max.
-
Trail attraction: V_tr(r) = ∫ e^(− r’ − r /σ)·G(r’) d²r’, where σ is visibility (sight range). -
Walking direction: e = ∇[U + V_tr]/ ∇[U + V_tr] , with destination potential U = − d − r . - Two dimensionless parameters: κ = I·T/σ and λ = V⁰·T/σ. Low κ gives a direct-route system, high κ a minimal (Steiner-like) system, and intermediate κ a “minimal detour system,” which matches photographs of real campus trails. Ants (chemical trails plus random search) instead produce dendritic trunk-and-branch systems.
- The paper gives no numeric parameter values other than a 100×100 lattice for the ant case.
- Trail design rules (US Forest Service, citing IMBA’s Trail Solutions, 2004): the “half rule” keeps trail grade at no more than half the grade of the sideslope it crosses; the “ten percent average guideline” keeps the average grade over an uphill section at or below about 10 percent. Maximum short-pitch grades of about 15 percent (15 to 20 percent in some sources) were found only in search summaries of secondary trail-standards documents. Switchback turns put their short apex in the fall line and need a grade reversal to shed water. — USFS: Trail design (htm07232806 p.4); Shuswap Trail Alliance design standards
Inferences
- Cost per grid step for walkers (implementable): cost = step_length × pace(S) / pace(0), where S = Δh/step_length signed in the direction of travel. Tobler is anisotropic (downhill at −5 percent is cheapest), so it suits A* edges directly. Add a multiplier for land cover (×1 on paths, ×5/3 off-path from Tobler’s 3/5 factor, ×3 to 5 in dense wood, ∞ for deep water and cliffs).
- Cost for carts and roads: a Galin-style capped slope cost, for example cost = len × (1 + a·(s/s_max)²) for s ≤ s_max and ∞ above, with s_max of 0.10 to 0.15 for roads (the 10 percent guideline) and 0.3 or more for footpaths. Switchbacks then appear automatically wherever the direct grade exceeds s_max, provided the neighbor mask is at least k = 2 (16 directions). With 8-connectivity, hairpins come out as ugly 45° stairs.
- On a phone: A* over 64×64 with a k = 2 or 3 mask is 4096 cells × 16 to 32 edges, well under 10 ms in JS. Skip the heading dimension (×12 cost). Instead, smooth the result afterward with Chaikin’s corner cutting or a string-pull pass, then rasterize to tiles.
- Fords and bridges: treat river cells as passable at cost F (ford) where depth is below a threshold and the river is wide and shallow, and add bridge edges as Galin-style long segments across a river at most L tiles wide, with a fixed cost B. With B large, roads converge on a single crossing, which is also where the village should grow (a “bridging point” situation bonus).
- Desire paths and game trails, cheap version of Helbing: keep a Float32 “wear” grid. For N simulated trips between points of interest (houses ↔ well, fields, ford; for animals, dens ↔ water), run A* where the step cost = base_cost × (1 − β·wear), with β of about 0.5 to 0.8. After each trip, add wear along the path (saturating at 1) and decay all wear by a factor e^(−Δt/T). After 20 to 100 trips, threshold the wear map into dirt-path tiles. This reproduces the κ behavior: high β and long T give a few shared trunk trails; low β gives many direct paths. Game trails to water emerge when trips start at random forest cells and end at the nearest bank cell.
- Roads should use w_ex-style reuse discounts (Emilien) or Galin’s proximity pruning so a village does not get a spiderweb of parallel lanes.
Gaps
- No numeric calibration for Helbing’s I, T or σ was found. Only qualitative regimes are published.
- No source on game-animal trail placement (for example, deer trails following contours to water) was found in the time available.
- The Galin 2011 merge criterion’s numeric thresholds were not extracted. Its timings were not captured.
Vegetation placement: ecological rules
Takeaway
Every documented system reduces to per-cell abiotic maps (elevation-derived temperature, sun exposure from aspect and shadow, soil moisture from flow and slope, soil depth from slope) fed into per-species tolerance curves combined by the minimum (Liebig’s law). Then come clearings around people and denser bands at edges. Cordonnier and Gain give exact formulas; Merryfolk shows a phone-scale version.
Cited Findings
- Viability hat function (Cordonnier et al. 2017, from Gain et al. 2017): for each plant type and each condition c ∈ {temperature, moisture, sunlight}: v(c) = −1 if c < E_min or c > E_max; (c − E_min)/(I_min − E_min) for E_min ≤ c < I_min; 1 for I_min ≤ c ≤ I_max; and (c − I_max)/(E_max − I_max) for I_max < c ≤ E_max. Viability is V = minᵢ vᵢ, “since viability is constrained by the weakest resource” (Liebig’s law of the minimum). — Cordonnier et al. 2017 PDF §5
- Note: as printed, the falling branch (c − I_max)/(E_max − I_max) runs from 0 to +1 rather than from +1 to 0. The intended falling ramp is presumably 1 − (c − I_max)/(E_max − I_max). This is my reading, not the paper’s.
- Inputs: temperature is the monthly mean minus a 6.5 °C/1000 m lapse; sun exposure is the average daily hours of direct sun per month, from ray casts along the sun’s path for a given latitude and compass direction, including terrain self-shadowing; soil moisture is the yearly moisture distributed by monthly rainfall. — Cordonnier et al. 2017 §5
- Vigor and stress: vigor is the mean viability over the growing season (months with mean temperature at least 5 °C); stress is the mean of the four worst monthly viabilities, counted only if negative. Germination adds seedlings in proportion to vigor × free space (1 − d), only if there is no stress, at an establishment rate of 0.24 saplings per m² for woody plants. Death removes plants until canopy density d < 1, then in proportion to stress, then a small random fraction above an age threshold. — Cordonnier et al. 2017 §5
- Canopy density per cell: d = n·π·(r·h/n)²/w², where n is the plant count, h the summed heights, r the canopy-radius-to-height ratio and w the cell width. d > 1 forces self-thinning. Shrubs under trees get sun reduced in proportion to tree density. Grass is a density, not individuals. — Cordonnier et al. 2017 §5
- Soil depth from slope: the initial humus thickness is H = g(slope), with g(s) = e^(−s²/σ²) and σ² = 3·ln 2, chosen so g(tan 30°) = 1/2: “half as much humus on 30° slopes as on flat areas.” — Cordonnier et al. 2017 §3.2
- Cordonnier’s layers are bedrock, rock, sand, humus, grass, shrubs, trees, plus dead vegetation that decays into humus (about 30 percent of litterfall becomes humus per year). Events (rain, temperature, lightning, gravity, fire) fire at random cells, with a time step of one year. Trees stabilize slopes against slides. — Cordonnier et al. 2017
- EcoBrush soil moisture: M_{i,m} = min(k_c, M_{i,m−1} + min(P_m, A_i) − k_p), where P_m is monthly rain, A_i the per-cell absorption, k_c the reservoir capacity and k_p plant uptake (auto-tuned so the yearly cycle balances). Slope linearly weights absorption, “to represent the transition from loamy soils in valleys to rock on cliff faces.” Rain above A_i runs off; surface water = runoff + seepage spread over the next three months. — Gain et al. 2017 PDF §4
- EcoBrush derives temperature (altitude lapse), sunlight (latitude plus terrain shadowing), soil moisture and slope maps, clusters cells by these conditions, and fills each cluster from a database of precomputed small simulations. It supports interactive design up to 5×5 km² and automatic generation of 10×10 km². — Gain et al. 2017; EG diglib record
- Hammes 2001 places plants at runtime from “ecotopes,” habitats with fairly uniform climate and soil, recomputed for each area that comes on screen, using algorithms chosen to be fast and deterministic. The detailed rules were not in the fetched material (search summary). — Hammes on flipcode; DOI 10.1007/3-540-44818-7_14
- Treeline: see the treeline finding in the first section (growing-season mean of about 6.4 °C; lower on the shaded ubac than on the adret). — GMBA; AMS Glossary: ubac
Inferences
- Phone recipe (one pass, no simulation):
temp = T0 − lapse·hsun = clamp(0.55 + 0.4·southness − small east penalty), which is Merryfolk’s formula, plus a shadow test against the cliff layers if cliffs existmoist = max(river term e^(−dist/4.5), TWI term)soilDepth = e^(−s²/(3 ln 2)), with s = tan(slope angle)- for each species, V = min(hat(temp), hat(moist), hat(sun), hat(soilDepth))
- pick a species by weighted random over max(0, V) × base abundance
- density = clamp(max V) × clearing factor
- Riparian strip: willows, alders and reeds at moist ≥ 0.85 and distance to water ≤ 1 to 2 cells. Treeline: temp below a threshold → V for trees falls to −1, so only grass, heather and rock remain. This puts the treeline lower on north-facing (ubac) slopes for free if
sunalso feedstemp. - Ecotones and forest edges: compute forest-ness F (a blurred tree-density map). Bushes and young trees concentrate where 0.15 < F < 0.6 (the edge band); deep forest gets ferns and holly. Merryfolk does exactly this with neighbor counts (1 to 4 trees in a 5×5 box = edge).
- Clearings round settlements: multiply tree potential by a ramp of BFS distance from settled tiles with noise added to the distance, for example clamp((d − 1.5 + noise·3)/6), so the edge is ragged. Paths get a 1-tile no-tree margin.
Gaps
- No primary ecology source was fetched for riparian strip widths or forest-edge widths in meters.
- Hammes 2001’s actual per-ecotope rules (it is widely cited for altitude, slope and relative elevation inputs) could not be confirmed from the full text.
Competition, spacing, and Poisson-disk with habitat-weighted density and species mixes
Takeaway
Spacing comes from either a circle-competition simulation (Deussen 1998, following Firbank & Watkinson: overlapping circles fight, the weaker dies) or direct sampling with a variable-radius Poisson disk. On a phone, do variable-radius dart throwing over a shuffled cell order (Merryfolk) or Bridson with a per-species radius. Run a few thinning steps only if size variety matters.
Cited Findings
- Deussen et al. 1998, “Realistic modeling and rendering of plant ecosystems” (SIGGRAPH ‘98, pp. 275–286). — PDF; project page
- Plants are represented “coarsely as circles positioned in a continuous landscape. Each circle defines the ecological neighborhood of the plant,” following Firbank and Watkinson.
- Self-thinning model: start with circles placed at random with random initial radii; circles grow; “If the circles representing two plants intersect, the smaller plant dies”; plants reaching a limit size are old and die. Average area against density follows a slope of −1 (log-log), equivalent to the −3/2 self-thinning law in mass. The demonstration started with 62,500 plants.
- Multi-species model: each species has (i) new plants added per step, (ii) maximum size, (iii) average growth rate, (iv) the probability of surviving domination, and (v) a wet or dry preference. Each plant has a species, a size and a vigor in [0, 1], set as a randomized function of local water and the species’ preference. Competitive ability = vigor × (size / max size). When circles intersect, the lower-ability plant is dominated and may die with the species’ probability. With eight species, segregation into wet and dry areas emerged.
- For designed scenes (orchards, parks), a painted density image is converted to positions by half-toning: “each dot becomes a plant.” Tree-crown projections can be rasterized to reduce the density or vigor of plants beneath.
- Bridson 2007: Poisson-disk sampling in O(N). Candidates are drawn from the annulus between r and 2r around an active sample, with up to k candidates per sample (“typically k = 30”), and a background grid of cell size r/√n for O(1) neighbor checks. — Bridson 2007 PDF
- EcoBrush synthesis: records pairwise distance histograms between plant categories (counts within concentric annular shells) and resynthesizes them by dart throwing or Metropolis-Hastings. It extends this to disk-disk (canopy radius) histograms so canopy-based spacing and undergrowth placement are reproduced. — Gain et al. 2017 §6
- Merryfolk’s variable-radius placement (local code): shuffle all tiles; for each grass tile not on or beside a road, accept with probability potential × 2.2 ×
gen('woods'); choose a species by weighted random; radius = RADIUS[kind] × (0.55 + 0.45 × the tree’s shape size), with RADIUS = {oak 2.1, beech 2.2, copper 2.2, birch 1.4, pine 1.6, fir 1.4, poplar 1.3, sapling 1.0} tiles; reject if any placed tree lies within 0.85 × max(r, r_other). Trunks get ±2 px jitter inside the tile. — /Users/david/projects/merryfolk/js/nature.js
Inferences
- The max(r₁, r₂) rule (Merryfolk) lets big trees hold space against small ones without a simulation, a cheap stand-in for Deussen’s dominance. Using (r₁ + r₂)/2 instead lets saplings crowd under oaks, which looks like undergrowth.
- Habitat-weighted Bridson: set the local radius r(p) = r_species / √(density(p)), store each sample’s radius, and test against max(r_new, r_old). Pick the species at candidate time from habitat weights. This gives clumps where habitat is good and sparse singletons at margins.
- Deterministic and chunk-safe: seed from
ctx.rng('trees'), or per chunk from hash(seed, cx, cy) asworld_generation.mddescribes. - Optional thinning for age structure: 5 to 10 iterations of “grow all radii by 10 percent; for each overlapping pair, the one with lower vigor × relative size dies with probability p_species.” With a few hundred trees and a spatial grid, this is milliseconds.
- At 16 px tiles on a 320×180 frame (20×11 tiles visible), only about 20 to 60 trees are on screen. Placement cost is negligible; the art budget (distinct tree sprites under 8 inks) is the real limit.
Gaps
- Firbank & Watkinson’s original parameters were not consulted.
- No benchmark of Bridson in JS on phones was found in this session.
How Merryfolk places oaks on loam, birches on thin soil and willows by water
Takeaway
Merryfolk (local) computes soil from height, slope, water distance and wetness, then uses per-species weight tables per soil class. Willows are placed by a separate pass on grass tiles adjacent to water. It is a compact, phone-proven template.
Cited Findings (all from local source)
-
Land analysis ( computeLand): slope =∇h by central differences; south = clamp(−dy/0.03, −1, 1) (“south, toward the bottom of the map, gets the sun”); sun = clamp(0.55 + 0.4·south − 0.05·max(0, dx/0.03), 0, 1); 8-neighbor BFS distance to water; Priority-Flood depression filling with a binary heap; D8 flow accumulation; TWI = ln(flow/(slope + 0.004)); moist = 1 on water, else clamp(0.62·e^(−distWater/4.5) + 0.5·t + gen(‘wet’)), where t = TWI normalized above 5. — /Users/david/projects/merryfolk/js/terrain.js - Soil classes: ALLUVIUM if h < 0.24 and distWater < 7; THIN if h > 0.62 or s > 1.1 (with s = slope/0.03); CLAY if moist > 0.4 and s < 0.6; otherwise LOAM. The comment describes it as “rich alluvium on the low flood ground, loam on gentle slopes, thin stony soil on the heights and steep banks, heavy clay where it’s wet and flat.” — /Users/david/projects/merryfolk/js/terrain.js
- Woodland potential: map border 0.65 (top) or 0.45; inside the named forest 0.35 + 0.02 × depth; otherwise by soil: THIN 0.28 + 0.12·slope, CLAY 0.26, ALLUVIUM 0.05, LOAM 0.04. Moisture above 0.85 multiplies by 0.4, and noise above 0.6 adds up to 0.8 × excess. The result is multiplied by the clearing ramp clamp((settled − 1.5 + (noise − 0.5)·3)/6, 0, 1). Loam and alluvium are thus mostly open (farmland), and woods sit on thin soil, clay and the map edges. — /Users/david/projects/merryfolk/js/nature.js
- Species weights: oak: LOAM 1, CLAY 0.9, ALLUVIUM 0.5, else 0.15; beech: LOAM or CLAY 0.45, else 0.06; birch: THIN 0.9, else 0.25 (+0.3 within 5 tiles of settlement, as a pioneer); pine: 0.8 on THIN with h > 0.5, else 0.05 (+0.5 at the forest edge or map edge); fir: 0.5 if h > 0.6 (+0.35 at edges); poplar: 0.6 if moist > 0.5 and soil is not THIN; sapling: 0.45 within 5 tiles of settlement, else 0.12. — /Users/david/projects/merryfolk/js/nature.js
- Willows: grass tiles with water in the 3×3 neighborhood, not reserved, not beside a road, no other tree within 4×3 tiles, accepted with probability 0.09. — /Users/david/projects/merryfolk/js/world.js
- Bushes: edge tiles (1 to 4 trees in the 5×5 box) get a bush with probability 0.16, deep woods 0.05, open ground 0.008. Thin soil with sun > 0.55 gets gorse or heather; deep or moist > 0.6 gets fern or holly; edges get bramble or thorn. Mushrooms go to the dampest wood tiles; wild herbs to sunny (> 0.5), dry (< 0.45) wood-edge tiles. — /Users/david/projects/merryfolk/js/nature.js; /Users/david/projects/merryfolk/js/world.js
Inferences
- Merryfolk’s tables are a discretized version of the Cordonnier/Gain hat functions: soil class stands in for moisture plus depth, and the edge and settlement bonuses stand in for succession (birch and saplings as pioneers near disturbance). A Tycho
habitatmodule could expose the land analysis (slope, aspect/sun, distance to water, Priority-Flood, flow, TWI, soil class) as reusable maps, with species tables left to each game or quest. - In an 8-ink frame, species must differ in silhouette more than color. Three to four species per quest (for example, oak, birch, willow, pine) is the practical ceiling.
Gaps
- None for the code itself. Its tuning constants are game-specific and not ecologically calibrated.
Drawing height in 3/4 oblique (top-down with front faces) pixel art
Takeaway
In 3/4 oblique, a raised area is drawn as its top surface shifted up the screen by height × (pixels per level), plus a front-facing vertical face below its southern edge. East and west edges get thin side faces or rims, and north edges get only a lip line, because the face points away. Height reads only with value contrast (lighter top, darker face, cast shadow on the ground at the foot), so a cliff is a stack of autotiled bands: lip, face rows, foot, shadow. Waterfalls are animated tiles that join an upper water edge to the cliff face and a lower pool. Caves are openings in south-facing faces leading to separate maps. True multi-level overlap (bridges you walk under) needs per-tile layer priority.
Cited Findings
- Cliff band structure (a commercial 16×16 tileset’s documentation): a tall cliff is four bands stacked lip to foot, plus a cast-shadow row on the ground; each band has six variants plus left and right end caps; “A drop taller than four tiles repeats the upper face band.” Lighting reads from the north-west: plateau edges have “a lit rim to the north and west, a shadowed face to the east, and a drop with a cast shadow to the south.” The plateau is a 47-mask (blob) autotile, with a second 47-mask set for rocky shelves on top. The pack ships no stairs or ramps. — Verdant 02 Elevation Tileset
- The same pack’s page argues (search summary) that a grey band between two identical lawns reads as a path, not a cliff. It recommends a lighter plateau surface, a stepped falloff down the face, and an offset cast shadow. — Verdant 02 Elevation Tileset
- Layer setup for cliffs, waterfalls, caves and stairs (tileset tutorial): Base Ground holds flat ground, water, waterfalls and ledges; Cliffs holds short cliffs, high cliff tops, cliff mid-sections, stairs and cave entrances; “Cliffs but Under” holds side sections of faces and the cliff piece visible beneath a cave entrance; Ground Carpet holds deep water, reflections and stones. A high waterfall has upper, middle and lower parts: the upper joins the cliff top horizontally, the middle the cliff face, and the lower the water vertically. It animates in a 3-frame loop; a “tiered” waterfall joins a short cliff to water, also in 3 frames. Stairs sit in the Cliffs layer with Base Ground visible at the top and bottom. — Biome tileset terrain set-up tutorial (itch.io devlog)
- Cave entrances: many tilesets include only north-facing cave entrances (openings in the face you see, on a cliff to the north of the player). The usual workaround is to put the entrance in a cliff face with more wall above it and the cave interior beyond (search summary). — OpenGameArt thread
- CrossCode made the game 3D “so that the player can walk on layers of different heights”; height matters, “jumping is not.” Jumps are low and automatic at ledges, partly because without perspective, objects at different heights are hard to place, so a small number of discrete height levels keeps ball physics readable. The game is “pseudo 3D without perspective distortion.” — Radical Fish: Why auto jump
- Eastward is “a 3D game with a 2D perspective,” built in a custom engine (Maoi) with lighting and fog layers. Assets drawn in Aseprite are split into parts and rebuilt in a 3D environment with hand-painted bump maps for correct lighting. On level design: “elevation is your friend to make an area feel dense!”; “Staircases in a bustling city let the player move behind buildings without being totally obscured”; verticality “can prevent elements in the foreground from occluding elements.” — Game Developer: Eastward’s creators
- Sea of Stars made climbing, vaulting and ledge-hoisting core traversal. Sabotage calls hoisting the key mechanic, giving “a lot more verticality in the gameplay than is usually seen in games of the genre,” and breaking from “bound-to-the-grid movement.” The Coral Cascades area is a cliffside crossed by jumping, swimming and shimmying along ledges. — PlayStation Blog; MobileSyrup interview with Thierry Boulanger; Nintendo Life hands-on
- Wargroove: a modder notes Wargroove “wasn’t supposed to have different layers/levels,” and the cliff and mountain tile “works like the wall tileset,” that is, a neighbor-selected autotile rather than true elevation. — Nexus Mods: Wargroove tileset mod
- SNES layering (relevant to A Link to the Past’s walk-under bridges and upper floors): in Mode 1, BG1 and BG2 are 16-color layers and BG3 a 4-color layer; each background splits into high- and low-priority halves by a per-tile attribute bit, so sprites can pass between them; a BGMODE bit brings BG3 to the front (used for status bars) (search summary). — SNESLab: BG3 High Priority; SNESdev wiki
- Tile size: “16x16px is probably the most common size,” and “Anything over 32x32px seems like overkill”; keep visual weight even within a tile; mirror tiles to make other orientations. — Slynyrd Pixelblog 20: Top Down Tiles
Inferences (reasoning and general practice, not sourced above)
- Data model: store an integer level L per tile (0 to 3 or so) separate from the material. Elsewhere’s 2:1 isometric “frogs’ rule” style differs from 3/4 oblique. These notes apply to a quest that chooses 3/4 oblique, or to Tycho generally.
- Rendering rule for 3/4 oblique: a tile at (x, y, L) draws its top at screen (x·T, y·T − L·H), where H is the face height per level (commonly H = T/2 to T, for example 8 or 16 px with 16 px tiles). Wherever L(x, y) > L(x, y+1), draw (L(x,y) − L(x,y+1))·H px of front face below the top’s south edge. Faces of a tile overdraw the top of the tile in front, so draw rows north to south (painter’s order by y, then by L). Sprites sort by their foot’s y plus L·H and are offset up by L·H.
- Autotile key: the cliff top is a 47-blob (or 16-mask) autotile over “same or higher level” neighbors. The face is chosen by a 3-bit key (left neighbor also has a face? right neighbor? is this the foot row?), giving the band variants and end caps the Verdant pack ships. Faces taller than the drawn bands repeat the middle band.
- Value rules under 8 inks: top = ground ink; lip = one step lighter (lit north and west rim); face = two steps darker, with a few vertical strata marks; foot = darkest ink line; cast shadow = one row of dither or darker ground on the tile below the foot, offset by the sun direction (ties into
sun_and_time_of_day.md). East edges get a 1 to 2 px dark side face; west edges get a lit rim. - Slopes and ramps without isometric: a ramp is a face drawn as diagonal steps or a gradient of 2 to 3 inks across the transition rows. Stairs are a stack of 2 to 4 px horizontal treads cut into a face. Gentle hills that are not cliffs can be shown by per-tile hillshading only: ink index = base + round(k·(∇h · light)), where light points north-west, quantized to 2 to 3 steps. This is cheap in an indexed
Inkbuffer and needs no faces. - Water at different levels: each pool is flat at its own level, and its edge against a face shows a 1-row bright waterline. A waterfall is a vertical strip of animated tiles (3 frames, per the tutorial) replacing a face segment, with a foam tile on the pool below. The river’s upper reach must end at the cliff lip on the waterfall column. Generation should place waterfalls where a flow path from Priority-Flood crosses a level drop.
- Caves: put entrances only in south-facing faces (the visible ones), at least 2 face rows tall; the interior is a separate map or quest scene. For cutaways, cut the north wall and face out of the drawing when the player is inside a building or behind a face (Merryfolk uses side-cut interiors).
- Walk-under bridges and overlaps need two draw layers per tile (below sprites, above sprites) and a per-actor layer flag switched at stairs, mirroring the SNES per-tile priority bit.
- Keep the level count small (CrossCode’s lesson) and make faces readable at 320×180: 2 to 4 levels, faces 8 to 16 px tall.
Gaps
- I found no primary developer write-ups (GDC talks, devlogs) for how Stardew Valley, Chained Echoes, Minish Cap or A Link to the Past build cliffs, ledges and stairs tile by tile. The A Link to the Past layering findings are general SNES hardware facts, not a disassembly of its bridges. The yaze ALttP documentation host did not resolve.
- No Eastward or Sea of Stars developer source describes the data format for heights (per-tile level, collision, sorting).
- No authoritative “ideal face height per level” figure was found. The 8 to 16 px values above are reasoning from 16 px tiles.
- Waterfall frame counts and caves come from asset-pack tutorials, not shipped-game sources.