From 3ab28138a03f178c321719acfcac1a1961c9cc4c Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 16 Apr 2026 08:42:32 +0200 Subject: [PATCH] feat(tooling): skip dedup-exhausted features instead of palette fallback (#833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When all 3 LLM attempts are rejected (dedup, blocklist, etc.), name_feature now returns None instead of a deterministic palette fallback. process_body leaves the name as null in markers.json. The preserved path (_is_blank) treats null as unnamed, so a fill round (re-running the script) picks up only the skipped features with a fresh corpus — zero dedup pressure from the first pass. The fill round can use a different seed, slower prompt, or a different backend entirely (e.g. Haiku). Co-Authored-By: Claude Opus 4.6 (1M context) --- tooling/planet-gen/gemma_naming.py | 64 ++++++++++++++---------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/tooling/planet-gen/gemma_naming.py b/tooling/planet-gen/gemma_naming.py index c3b880d5e..4cbae2ab7 100755 --- a/tooling/planet-gen/gemma_naming.py +++ b/tooling/planet-gen/gemma_naming.py @@ -1450,21 +1450,12 @@ def name_feature( _commit_name(cleaned) return cleaned - # All attempts exhausted — deterministic palette fallback, then dedup. - # Fallback names draw from the palette stems and do NOT count against - # the stem cap (the palette is intentionally narrow and would trigger - # infinite rejection loops otherwise). - salt = _seed_for(world_seed, body_id, local_id, max_attempts) & 0xFFFFFF - fallback = fallback_name(corridor, feature_type, salt) - bump = 0 - while _is_duplicate(fallback) and bump < 100: - bump += 1 - fallback = fallback_name(corridor, feature_type, salt + bump) - used.add(fallback) - body_used.add(fallback) + # All attempts exhausted. Leave the name as None so the preserved + # path skips it on a fill round — a second pass with a fresh corpus + # will pick it up and try again with different dedup pressure. reasons = " ".join(f"{k}={v}" for k, v in rejections.items() if v > 0) - log(f" fallback: {body_id}/{local_id} → '{fallback}' [{reasons}]") - return fallback + log(f" skipped: {body_id}/{local_id} [{reasons}]") + return None # --------------------------------------------------------------------------- @@ -1646,10 +1637,11 @@ def process_body( world_seed, body_id, city.get("id") or "city_?", hop, log, verbose, ) - city["name"] = name - counts["cities"] += 1 - generated["cities"].append(name) - changed = True + if name is not None: + city["name"] = name + counts["cities"] += 1 + generated["cities"].append(name) + changed = True # Rivers for river in markers.get("rivers") or []: @@ -1663,10 +1655,11 @@ def process_body( world_seed, body_id, river.get("id") or "river_?", hop, log, verbose, ) - river["name"] = name - counts["rivers"] += 1 - generated["rivers"].append(name) - changed = True + if name is not None: + river["name"] = name + counts["rivers"] += 1 + generated["rivers"].append(name) + changed = True # Oceans / seas / lakes for water in markers.get("oceans") or []: @@ -1683,10 +1676,11 @@ def process_body( world_seed, body_id, water.get("id") or "water_?", hop, log, verbose, ) - water["name"] = name - counts["oceans"] += 1 - generated["oceans"].append(name) - changed = True + if name is not None: + water["name"] = name + counts["oceans"] += 1 + generated["oceans"].append(name) + changed = True # Mountain ranges for rng_feat in markers.get("mountain_ranges") or []: @@ -1702,10 +1696,11 @@ def process_body( world_seed, body_id, rng_feat.get("id") or "range_?", hop, log, verbose, ) - rng_feat["name"] = name - counts["mountain_ranges"] += 1 - generated["mountain_ranges"].append(name) - changed = True + if name is not None: + rng_feat["name"] = name + counts["mountain_ranges"] += 1 + generated["mountain_ranges"].append(name) + changed = True # POIs for poi in markers.get("pois") or []: @@ -1722,10 +1717,11 @@ def process_body( world_seed, body_id, poi.get("id") or "poi_?", hop, log, verbose, ) - poi["name"] = name - counts["pois"] += 1 - generated["pois"].append(name) - changed = True + if name is not None: + poi["name"] = name + counts["pois"] += 1 + generated["pois"].append(name) + changed = True if changed: markers_path.write_text(json.dumps(markers, indent=2) + "\n")