fix(assets): drop atomic writes, fix heightmap-size flag, default to 1024x512

The atomic .tmp→rename pattern caused silent FileNotFoundError on some
bodies. Removed in favour of direct writes — resume logic already handles
interrupted runs. Fixed --heightmap-size CLI flag which was silently
ignored due to Python default parameter binding. Changed default heightmap
resolution from 4096x2048 to 1024x512 (native simulation grid — no
information gain from upscaling).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 18:51:00 +02:00
co-authored by Claude Opus 4.6
parent db241b88bd
commit 53ec7ee6d9
3 changed files with 26 additions and 45 deletions
+15 -30
View File
@@ -186,18 +186,14 @@ def _scaffold_system(system_dir: Path, overrides: dict) -> list:
# Generate
# ─────────────────────────────────────────────────────────────────────────────
def _atomic_save_img(img, target: Path):
"""Write image to .tmp, then rename. Prevents corrupt files on crash."""
tmp = target.with_name(target.name + ".tmp")
img.save(str(tmp), format="PNG")
tmp.rename(target)
def _save_img(img, target: Path):
"""Write image directly — resume logic handles incomplete outputs."""
img.save(str(target), format="PNG")
def _atomic_save(data, target: Path, save_fn):
"""Write data to .tmp, then rename. Prevents corrupt files on crash."""
tmp = target.with_name(target.name + ".tmp")
save_fn(data, str(tmp))
tmp.rename(target)
def _save(data, target: Path, save_fn):
"""Write data directly — resume logic handles incomplete outputs."""
save_fn(data, str(target))
def _is_complete(body_dir: Path, is_gas: bool) -> bool:
@@ -242,21 +238,15 @@ def _generate_body_from_dir(body_dir: Path, hmap_w: int, hmap_h: int,
# Heightmap — atomic write
if not is_gas:
import render_heightmap as rh
rh.OUT_W = hmap_w
rh.OUT_H = hmap_h
rh.UI_SCALE = hmap_w / 1024
rh.RENDER_MODE = "cartographic"
rh.BIOME_RGB = rh._build_biome_rgb("cartographic")
rh.OCEAN_DEEP, rh.OCEAN_MID, rh.OCEAN_SHALLOW = rh._ocean_arrays("cartographic")
hmap_img = render_heightmap(bd, terrain, chrome=False)
_atomic_save_img(hmap_img, body_dir / "heightmap.png")
hmap_img = render_heightmap(bd, terrain,
out_w=hmap_w, out_h=hmap_h,
chrome=False)
_save_img(hmap_img, body_dir / "heightmap.png")
# Globe — atomic write
from planet_renderer import render_globe
globe_img = render_globe(bd, terrain, size=globe_size)
_atomic_save_img(globe_img, body_dir / "globe.png")
_save_img(globe_img, body_dir / "globe.png")
# Terrain data — atomic write
if not is_gas:
@@ -266,17 +256,12 @@ def _generate_body_from_dir(body_dir: Path, hmap_w: int, hmap_h: int,
if key in terrain:
save_dict[key] = terrain[key]
save_dict["sea_level"] = np.array([terrain["sea_level"]])
# npz: numpy appends .npz to the path, so write directly
# (atomic rename doesn't work cleanly with numpy's extension handling)
npz_path = body_dir / "terrain.npz"
npz_tmp = body_dir / "terrain_tmp"
np.savez_compressed(str(npz_tmp), **save_dict)
Path(str(npz_tmp) + ".npz").rename(npz_path)
np.savez_compressed(str(body_dir / "terrain.npz"), **save_dict)
# Markers — atomic write
# Markers
from generate import _build_markers
markers = _build_markers(bd, terrain)
_atomic_save(markers, body_dir / "markers.json",
_save(markers, body_dir / "markers.json",
lambda m, p: Path(p).write_text(json.dumps(m, indent=2)))
elapsed = time.time() - t0
@@ -303,7 +288,7 @@ def main():
help="Validate body definitions without generating")
parser.add_argument("--verify-determinism", type=int, metavar="N", default=0,
help="Run N random bodies twice and verify identical output")
parser.add_argument("--heightmap-size", default="4096x2048")
parser.add_argument("--heightmap-size", default="1024x512")
parser.add_argument("--globe-size", type=int, default=512)
args = parser.parse_args()
+7 -11
View File
@@ -151,21 +151,17 @@ def _generate_body(body_def: dict, hmap_w: int, hmap_h: int,
# ── 2. Render heightmap ──────────────────────────────────────────────
t_hmap = t_sim
if not is_gas:
import render_heightmap as rh
rh.OUT_W = hmap_w
rh.OUT_H = hmap_h
rh.UI_SCALE = hmap_w / 1024
rh.RENDER_MODE = render_mode
rh.BIOME_RGB = rh._build_biome_rgb(render_mode)
rh.OCEAN_DEEP, rh.OCEAN_MID, rh.OCEAN_SHALLOW = rh._ocean_arrays(render_mode)
# Clean heightmap (no title/legend)
hmap_img = render_heightmap(body_def, terrain, chrome=False)
hmap_img = render_heightmap(body_def, terrain,
out_w=hmap_w, out_h=hmap_h,
render_mode=render_mode, chrome=False)
hmap_img.save(os.path.join(body_dir, "heightmap.png"))
# Chrome version for review (optional — saved to /tmp, not shipped)
if chrome:
hmap_chrome = render_heightmap(body_def, terrain, chrome=True)
hmap_chrome = render_heightmap(body_def, terrain,
out_w=hmap_w, out_h=hmap_h,
render_mode=render_mode, chrome=True)
chrome_path = f"/tmp/{body_id}_heightmap_chrome.png"
hmap_chrome.save(chrome_path)
print(f" chrome: {chrome_path}")
@@ -223,7 +219,7 @@ def main():
help="Root output directory (bodies get subdirs)")
# Rendering
parser.add_argument("--heightmap-size", default="4096x2048",
parser.add_argument("--heightmap-size", default="1024x512",
help="Heightmap output resolution (WxH)")
parser.add_argument("--globe-size", type=int, default=512,
help="Globe output resolution (square, locked at 512)")
+4 -4
View File
@@ -52,8 +52,8 @@ from biome_config import (
# Output resolution
# ---------------------------------------------------------------------------
OUT_W = 4096
OUT_H = 2048
OUT_W = 1024
OUT_H = 512
UI_SCALE = OUT_W / 1024 # 4.0 — all pixel sizes scale with this
# ---------------------------------------------------------------------------
@@ -390,13 +390,13 @@ def render_heightmap(body_def: dict,
render_mode: str = "cartographic",
chrome: bool = True) -> Image.Image:
"""
Render a 4096×2048 annotated equirectangular heightmap PNG.
Render an annotated equirectangular heightmap PNG.
Parameters
----------
body_def : dict — body definition from body_definition_parser
terrain : dict — terrain dict from planet_simulation.simulate()
out_w, out_h — output resolution (default 4096×2048)
out_w, out_h — output resolution (default 1024×512)
Returns
-------