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:
+15
-30
@@ -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()
|
||||
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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
|
||||
-------
|
||||
|
||||
Reference in New Issue
Block a user