diff --git a/tooling/planet-gen/gemma_naming.py b/tooling/planet-gen/gemma_naming.py index 94bf7a6d6..c3b880d5e 100755 --- a/tooling/planet-gen/gemma_naming.py +++ b/tooling/planet-gen/gemma_naming.py @@ -1310,7 +1310,7 @@ def name_feature( feature_type: str, ctx: dict, blocklist: set[str], - corpus: dict[tuple[str, str], set[str]], + corpus: dict[tuple, set[str]], body_used: set[str], stem_counts: dict[str, int], stem_cap: int, @@ -1318,22 +1318,23 @@ def name_feature( world_seed: int, body_id: str, local_id: str, + hop: int, log: "Logger", verbose: bool, max_attempts: int = 3, ) -> str: - """Request a name from Gemma, enforce blocklist + corridor dedup + + """Request a name from Gemma, enforce blocklist + hop/corridor dedup + per-body cross-type dedup, fall back to the palette generator on persistent failure. Dedup scopes: - - `corpus[(corridor, feature_type)]` — cross-body dedup within the - same corridor and feature type. Two rivers in the north_reach - should not share a name; two rivers on opposite arcs can. + - `corpus[(hop, corridor, feature_type)]` — cross-body dedup within + the same gate-hop distance and corridor. Systems at the same hop + in the same corridor are near neighbors and shouldn't share + feature names. Systems at different hops can. - `body_used` — per-body set across ALL feature types. Prevents the same name from appearing as a river AND an ocean AND a - mountain range on the same world, which reads as ridiculous - even when the types differ. + mountain range on the same world. """ corridor = ctx.get("cultural_corridor") or "core" if feature_type not in _PROMPT_CONFIG: @@ -1342,7 +1343,7 @@ def name_feature( palette = palette_for(corridor) planet_class = ctx.get("planet_class") or "habitable" - dedup_key = (corridor, feature_type) + dedup_key = (hop, corridor, feature_type) used = corpus.setdefault(dedup_key, set()) # Capture mode: build the attempt-0 prompt, log it, return a unique @@ -1578,11 +1579,12 @@ def process_body( voice: VoiceSubprocess, conn: sqlite3.Connection, blocklist: set[str], - corpus: dict[tuple[str, str], set[str]], + corpus: dict[tuple, set[str]], stem_counts: dict[str, int], stem_cap: int, system_hook: str | None, world_seed: int, + hop: int, log: "Logger", verbose: bool, ) -> dict: @@ -1632,7 +1634,7 @@ def process_body( # Cities for city in markers.get("cities") or []: if not _is_blank(city.get("name")): - corpus.setdefault((corridor, _feature_type_for_city(city)), set()).add( + corpus.setdefault((hop, corridor, _feature_type_for_city(city)), set()).add( city["name"] ) counts["preserved"] += 1 @@ -1642,7 +1644,7 @@ def process_body( voice, feature_type, ctx, blocklist, corpus, body_used, stem_counts, stem_cap, system_hook, world_seed, body_id, city.get("id") or "city_?", - log, verbose, + hop, log, verbose, ) city["name"] = name counts["cities"] += 1 @@ -1652,14 +1654,14 @@ def process_body( # Rivers for river in markers.get("rivers") or []: if not _is_blank(river.get("name")): - corpus.setdefault((corridor, "river"), set()).add(river["name"]) + corpus.setdefault((hop, corridor, "river"), set()).add(river["name"]) counts["preserved"] += 1 continue name = name_feature( voice, "river", ctx, blocklist, corpus, body_used, stem_counts, stem_cap, system_hook, world_seed, body_id, river.get("id") or "river_?", - log, verbose, + hop, log, verbose, ) river["name"] = name counts["rivers"] += 1 @@ -1669,7 +1671,7 @@ def process_body( # Oceans / seas / lakes for water in markers.get("oceans") or []: if not _is_blank(water.get("name")): - corpus.setdefault((corridor, _feature_type_for_ocean(water)), set()).add( + corpus.setdefault((hop, corridor, _feature_type_for_ocean(water)), set()).add( water["name"] ) counts["preserved"] += 1 @@ -1679,7 +1681,7 @@ def process_body( voice, feature_type, ctx, blocklist, corpus, body_used, stem_counts, stem_cap, system_hook, world_seed, body_id, water.get("id") or "water_?", - log, verbose, + hop, log, verbose, ) water["name"] = name counts["oceans"] += 1 @@ -1689,7 +1691,7 @@ def process_body( # Mountain ranges for rng_feat in markers.get("mountain_ranges") or []: if not _is_blank(rng_feat.get("name")): - corpus.setdefault((corridor, "mountain_range"), set()).add( + corpus.setdefault((hop, corridor, "mountain_range"), set()).add( rng_feat["name"] ) counts["preserved"] += 1 @@ -1698,7 +1700,7 @@ def process_body( voice, "mountain_range", ctx, blocklist, corpus, body_used, stem_counts, stem_cap, system_hook, world_seed, body_id, rng_feat.get("id") or "range_?", - log, verbose, + hop, log, verbose, ) rng_feat["name"] = name counts["mountain_ranges"] += 1 @@ -1708,7 +1710,7 @@ def process_body( # POIs for poi in markers.get("pois") or []: if not _is_blank(poi.get("name")): - corpus.setdefault((corridor, _feature_type_for_poi(poi)), set()).add( + corpus.setdefault((hop, corridor, _feature_type_for_poi(poi)), set()).add( poi["name"] ) counts["preserved"] += 1 @@ -1718,7 +1720,7 @@ def process_body( voice, feature_type, ctx, blocklist, corpus, body_used, stem_counts, stem_cap, system_hook, world_seed, body_id, poi.get("id") or "poi_?", - log, verbose, + hop, log, verbose, ) poi["name"] = name counts["pois"] += 1 @@ -2008,6 +2010,7 @@ def main(): seen_systems.add(system_id) t0 = time.time() + hop = hop_order.get(body_id, (99, ""))[0] counts = process_body( body_id=body_id, system_id=system_id, @@ -2020,6 +2023,7 @@ def main(): stem_cap=args.stem_cap, system_hook=system_gttr_hooks.get(system_id), world_seed=args.seed, + hop=hop, log=log, verbose=args.verbose, )