Documentation

Boulder fields

Procedural rock populations with a geological basis. A rock group in bottom.rocks becomes a boulder field when it names a preset or sets any of the knobs below; a group with neither runs the plain rock populator exactly as before. Implementation: simulator/boulder_field.py.

Presets

Preset Site Lithology, shape Sizes (m), alpha Pattern Burial
glacial_till till plain, moraine granite, rounded 0.10 to 3.0, 2.8 clusters, scale 6 m, 0.004 clusters per m2 30 to 70 %
dropstones ice-rafted erratics on soft bed granite, rounded 0.5 to 4.0, 2.3 sparse Poisson 40 to 80 %, scour on
talus scree at the foot of a cliff basalt, angular 0.2 to 2.0, 2.5 band from y_min, 6 m e-fold 0 to 20 %
rockfall fan below a rock face basalt, angular 0.3 to 5.0, 2.2 band from y_min, 15 m, big rocks travel furthest 0 to 30 %
cobble_bed current-swept lag deposit sandstone, rounded 0.05 to 0.4, 2.6 near-uniform, long axes at 90 deg 20 to 50 %
slab_field broken sedimentary pavement sandstone, platy 0.3 to 3.0, 2.4 clusters, scale 4 m, long axes at 0 deg 10 to 40 %
reef_rubble storm-broken carbonate carbonate rubble, subangular 0.05 to 0.8, 2.7 patchy clusters, scale 3 m 10 to 40 %
basalt_columns columnar jointing basalt, prismatic hexagons 0.3 to 1.0 across, 2.0 tight patches, scale 2 m 0 to 30 %

Each preset carries a default density. Setting density on the group overrides it. Under a preset, density, d_min, d_max and alpha left at their plain-rock defaults defer to the preset; any other value overrides it. The runner prints a note when this happens, naming the field, the default it ignored and the preset value it used.

bottom:
  rocks:
    - {bbox: [20, 30, 60, 70], preset: glacial_till, seed: 501}
    - {bbox: [0, 0, 80, 12], preset: talus, source_edge: y_min, density: 2.0, seed: 502}
    - {zones: [[1, 0], [1, 1]], preset: reef_rubble, burial_min: 0.3, burial_max: 0.6, seed: 503}
    - {preset: dropstones, scour: {enabled: true, current_speed_ms: 0.7, current_heading_deg: 30}, seed: 504}

Knobs

Every knob is optional; null or absent means "the preset's value".

Knob Meaning
lithology granite, basalt, limestone, sandstone, carbonate_rubble. Sets a target-strength offset, specular fraction, surface roughness and a default shape.
shape rounded, subangular, angular, platy, prismatic. Zingg axis-ratio family plus facet count (below).
cluster_scale_m, cluster_density Thomas cluster process: parents per m2 and the Gaussian spread of rocks around each parent. Set both or neither. Mutually exclusive with source_edge: a band from an edge and a cluster process are two different point processes, and asking for both raises.
source_edge, band_width_m Exponential band of rocks from one bbox edge (x_min, x_max, y_min, y_max); the e-folding distance in metres. Mutually exclusive with the cluster knobs.
size_grows_with_distance Rockfall sorting: larger rocks land further from the source edge.
burial_min, burial_max Burial as a fraction of the rock's vertical semi-axis, drawn per rock. Exposed height sets the shadow length.
heading_deg, heading_spread_deg Long-axis heading (degrees, NED) and its spread; omit heading_deg for uniform headings.
scour, scour_d_min Steady-current scour block (enabled, current_speed_ms, current_heading_deg, sediment_type) applied around rocks at least scour_d_min metres across, largest first, at most 500 per group. The pit tapers to zero at the rock footprint at the sand's angle of repose (32 degrees) rather than ending in a hard edge.
ts_offset_db, specular_fraction Override the lithology's acoustic values.

Switching a preset feature off

null means "the preset's value", so turning a feature the preset has on back off takes a value of its own:

Off encoding Effect
cluster_density: 0 (or cluster_scale_m: 0) Clusters off; positions are a homogeneous Poisson field.
source_edge: none No band from an edge; positions fill the region.
heading_deg: uniform Uniform random long-axis headings.
scour: {enabled: false} No scour pits.

Shape families (Zingg 1935)

Family b/a c/b Clipping planes Body
rounded 0.65 to 1 0.55 to 1 none superellipsoid, exponent 2.0 to 2.6
subangular 0.60 to 1 0.50 to 1 2 to 4 exponent 2.0 to 3.0
angular 0.55 to 1 0.45 to 1 4 to 8 exponent 2.5 to 4.0
platy 0.60 to 1 0.15 to 0.35 2 to 5 exponent 2.5 to 4.0
prismatic 1 height 0.6 to 2.5 of width 6 sides + tilted top hexagonal column, exponent 12

Every body is convex. Angularity comes from random clipping planes, never from a superellipsoid exponent below 2 (which would pinch the rock into a diamond). Sizes follow a truncated power law p(D) ~ D^-alpha between d_min and d_max; fragment populations have alpha between about 2 and 3.5 (Turcotte 1986). Wentworth (1922) size classes: cobble 64 to 256 mm, boulder above 256 mm.

What the SAS image shows

Each rock is a bright highlight on its near face and an acoustic shadow behind it whose length is set by the exposed height. Burial shortens the shadow; scour adds a dark pit on the upstream side and a faint mound downstream; the pit slopes up to the rock at the sand's angle of repose (32 degrees) rather than cutting a hard edge. Clusters read as bright patches with speckled interiors; talus reads as a bright band that thins with distance from the source.

Limits

  • Rocks seat on the local bed like every other object, writing only their own footprint on a datum taken from the mean bed under it, so nothing leaves a square pedestal (realism notes section 5); PS_LEGACY_SEAT=1 restores the old window stamp at z = 0 for one release.
  • No bedrock outcrops or ledges; talus and rockfall take a bbox edge as their source.
  • No overhangs (the heightfield is column monotone).
  • A group over 50 000 rocks prints a warning; over 200 000 raises.
  • Scour is the steady-current model of simulator/scour.py (no waves).

Performance

Cost of placing one rock: a 0.3 m body on a 0.05 m grid at 200 scatterers per m2, 50 repetitions each, measured on 2026-09-17 on the development machine (numpy 2.3.5, scipy 1.16.3, CPython 3.12.12, one core).

Rock ms per rock
plain rounded, not buried (the legacy placer) 3.0
rounded, buried (sink_m > 0) 3.5
faceted, buried 1.7

A buried rounded rock costs a little more than an unburied one because its mesh is a convex hull of surface samples rather than the analytic ring mesh. A faceted rock is cheaper than either: its hull is built from a coarser polar grid. Meshes dominate the per-rock cost, so a field of tens of thousands of rocks is minutes, not seconds.

References

  • Wentworth, C.K. (1922). A scale of grade and class terms for clastic sediments. J. Geology 30(5).
  • Zingg, T. (1935). Beitrag zur Schotteranalyse. Schweiz. Mineral. Petrogr. Mitt. 15.
  • Turcotte, D.L. (1986). Fractals and fragmentation. J. Geophys. Res. 91(B2).
  • Sumer, B.M. and Fredsoe, J. (2002). The Mechanics of Scour in the Marine Environment. World Scientific.