"""Config O₂ / low-gravity zones: smooth, noise-warped discs in mask units
(O₂ ×(1 + 0.5 v), gravity ×(1 + 0.7 v)), added to the painted masks in the `sketch` stage. Very gradual: at most
≈ 1.9 · |effect| / radius per km (≤ 0.23 O₂ points and ≤ 0.014 g per 30 km for the configured zones)."""
from __future__ import annotations
import numpy as np
from .noise import fbm, name_seed
from .sphere import gc_dist_km, latlon_to_xyz
WARP = 0.2 # outline wobble: distances stretched or shrunk by up to 20 % (low-frequency noise)
WARP_FREQ = 1.0
def smootherstep(t):
t = np.clip(t, 0.0, 1.0)
return t * t * t * (t * (6.0 * t - 15.0) + 10.0)
def contribution(xyz, zone: dict, seed: int, radius_km: float):
"""v · (1 − smootherstep(d′ / r)), d′ = d · (1 + WARP · warp): v at the centre, 0 beyond r / (1 − WARP)."""
xyz = np.asarray(xyz, dtype=np.float64)
d = gc_dist_km(xyz, latlon_to_xyz(*zone["center"]), radius_km)
r = float(zone["radius_km"])
out = np.zeros(len(xyz))
near = d < r / (1.0 - WARP)
if near.any():
w = np.clip(2.0 * fbm(xyz[near], name_seed(seed, zone["name"]), 3, WARP_FREQ), -1.0, 1.0)
out[near] = zone["v"] * (1.0 - smootherstep(d[near] * (1.0 + WARP * w) / r))
return out
def masks(xyz, zones: list, seed: int, radius_km: float):
"""(m_o2, m_gravity): the config zones summed (the caller adds the painted masks and clips to [−1, 1])."""
m = {"o2": np.zeros(len(xyz)), "gravity": np.zeros(len(xyz))}
for z in zones:
m[z["field"]] += contribution(xyz, z, seed, radius_km)
return m["o2"], m["gravity"]