From a5fbce4cc9ecd26ea81f56d60002a793680d63a5 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 15 Apr 2026 12:37:52 +0200 Subject: [PATCH] feat(tooling): per-system gttr context + cosmopolitan corridor palettes (#833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related quality fixes observed mid-run on Sirius + ACB + Ran: 1) Cosmopolitan corridor palettes. The six corridor inflection labels were single-culture dominant ("administrative English / Gateway-era", "British / Australian / Irish", "Korean/Japanese/Taiwanese", etc). Gemma 2 2B interpreted these as "produce ONLY in this register" and every core body came out anglophone, every east_reach body came out East Asian. The real Earth diaspora in the setting is cosmopolitan — a British surveyor on an east_reach moon still names a river after their aunt in Dorset. The labels now spell out the dominant register AND explicitly invite cross-cultural variety so Gemma samples from the full few-shot pool instead of collapsing to one culture. 2) Per-system gttr context (the big one). The gttr.md files under wiki/star-systems//gttr.md already carry a vivid one-sentence characterisation of every system — "where the rules live", "forty years old and still in the draft", "the most connected system in the Reach", "grandparents owned the land". This is a far stronger cultural signal than the corridor inflection alone. New column `star_systems.gttr_hook` stores a pre-extracted 45-word hook per system. `tooling/db/populate_gttr_hook.py` parses each gttr.md, regex-matches the first `**NAME**` paragraph, normalises whitespace, truncates softly at a word cap, and stores it. Covers all 301 systems (full coverage). Idempotent, safe to re-run after any wiki update. Explicit transaction wrapper. gemma_naming.py loads the hook cache at startup via `load_system_gttr_hooks` and threads `system_hook` plus the system and body proper names through process_body → name_feature → _build_prompt. The prompt now carries: System: . Planet: . About the system: Style: British. Answer: Cooper's Creek Style: Dutch. Answer: Meijer Beek ... Real-mode smoke on 10 cases across 4 contrasting systems shows the hook is doing exactly what it should. Sample output on the same body_id / local_id pairs: Tau Ceti (cosmopolitan hub) → Oakham River, Riverwood, Bridle Way Ran (old-family agricultural) → Hart's Well, Blackwood Ridge ACB (Lattice Commission seat) → Greenhaven, Rudge Brook Posto Avançado (PT frontier dead-end) → Rio Preto, Serra de Caxias, Cunha's Cove Posto Avançado went from "likely-English under the old corridor-only prompt" to actual Portuguese names with a real Brazilian place stem (Caxias), because the hook explicitly mentions wave_5 Portuguese founders and frontier dead-end context. The gttr cultural one-liner is the single strongest lever available for per-system cohesion — this was the mono-culture issue observed in the first run, now fixed. Token cost: ~60-90 extra tokens per prompt (hook + ident line). Inference slowdown: ~5-10% per call. Acceptable for the quality gain. Also restores 10 markers.json files that were stale from the aborted run just killed — they were all core bodies at hop 0-1 which benefit most from the gttr-context upgrade, so re-running them with the new prompt is worth the ~3 minutes of re-inference. --- server/data/systems-schema.sql | 9 ++ server/data/systems.db | Bin 3772416 -> 3862528 bytes tooling/db/populate_gttr_hook.py | 215 +++++++++++++++++++++++++++++ tooling/planet-gen/gemma_naming.py | 135 ++++++++++++++---- 4 files changed, 333 insertions(+), 26 deletions(-) create mode 100755 tooling/db/populate_gttr_hook.py diff --git a/server/data/systems-schema.sql b/server/data/systems-schema.sql index 796b7933c..fa58aac7c 100644 --- a/server/data/systems-schema.sql +++ b/server/data/systems-schema.sql @@ -38,6 +38,15 @@ CREATE TABLE IF NOT EXISTS star_systems ( cultural_corridor TEXT, generation_priority TEXT, + -- Short narrative hook extracted from the wiki's gttr.md file — the + -- first characterisation paragraph ("where the rules live", "forty + -- years old and still in the draft", etc). Populated by + -- tooling/db/populate_gttr_hook.py from wiki/star-systems//gttr.md. + -- Used as compact cultural context in the Gemma 2 naming pipeline + -- (#833) so per-system feel is grounded in the canonical identity + -- rather than generic corridor labels. + gttr_hook TEXT, + -- Economics (D-172) currency_zone TEXT DEFAULT 'TRACTUS_PRIMARY', -- TRACTUS_PRIMARY | MARK_PRIMARY | MIXED diff --git a/server/data/systems.db b/server/data/systems.db index fd767e7bdd55c442f46a201e72dbca1c48a5622e..261aa54a5cf646b4f497ca543e15c0d98e749bbd 100644 GIT binary patch delta 94937 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π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

OM3yaCEr$ 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 delta 16655 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 diff --git a/tooling/db/populate_gttr_hook.py b/tooling/db/populate_gttr_hook.py new file mode 100755 index 000000000..c5992de68 --- /dev/null +++ b/tooling/db/populate_gttr_hook.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Extract a short narrative hook from every wiki/star-systems//gttr.md +and store it on `star_systems.gttr_hook`. + +The GTTR ("Drifter's Guide to the Reach") files follow a consistent +format: an H1 title, then a paragraph that opens with `**NAME**` followed +by a 1-3 sentence characterisation. That characterisation is the hook — +per D-191 §4 and the #833 naming pipeline it is the single strongest +cultural signal available for each system, capturing things like: + + - Gateway: "the most connected system in the Reach, the site of its + oldest research institution, and the location of a gate that goes + to Earth and that nobody uses" + - Ran: "a very nice place to live, provided you are the sort of + person whose grandparents owned the land" + - ACB: "where the Lattice Commission lives, which means it is where + the rules live" + - Posto Avançado: "forward post — the place beyond the established + line" + +This script parses each gttr.md, regex-extracts the first `**NAME**` +paragraph, normalises whitespace, and truncates at a soft word cap so +the hook stays cheap to inject into prompts. Empty or unmatched files +leave the column NULL. + +Idempotent, safe to re-run after any wiki update. Explicit transaction +wrapper with rollback on exception. + +Usage: + tooling/db/populate_gttr_hook.py + tooling/db/populate_gttr_hook.py --max-words 45 + tooling/db/populate_gttr_hook.py --dry-run + tooling/db/populate_gttr_hook.py --system "GJ 71" +""" + +import argparse +import re +import sqlite3 +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = (SCRIPT_DIR / ".." / "..").resolve() +DB_PATH = REPO_ROOT / "server" / "data" / "systems.db" +WIKI_SYSTEMS = REPO_ROOT / "wiki" / "star-systems" + +# Matches the first paragraph that opens with `**NAME**` at the start +# of a line. Captures everything up to a blank line or the next H1/H2. +# Non-greedy on the content. +# Match a line that opens with `**NAME**` (any non-asterisk chars, since +# names contain accents from many alphabets — Á, Ž, Ç, Ñ, etc) followed +# by the paragraph body up to a blank line or the next markdown heading. +_HOOK_RE = re.compile( + r"^\*\*([^\n*]+?)\*\*(.+?)(?:\n\n|\n#)", + re.DOTALL | re.MULTILINE, +) + + +def _system_slug_to_system_id(slug: str) -> str: + """Convert 'GJ-244A' → 'GJ 244A'. Mirrors populate_terrain_reference.py.""" + if slug.startswith("GJ-"): + return "GJ " + slug[3:] + return slug + + +def extract_hook(gttr_path: Path, max_words: int) -> str | None: + """Return a compact one-paragraph hook for this gttr.md, or None if + the file is missing or the opening paragraph can't be located.""" + if not gttr_path.exists(): + return None + text = gttr_path.read_text() + + match = _HOOK_RE.search(text) + if not match: + return None + + name_token = match.group(1).strip() + body = match.group(2).strip() + + # Reconstruct "NAME is …" without the markdown asterisks. Add a + # single space between the name and the body since `.strip()` above + # removed the leading space from the captured body. + hook = f"{name_token} {body}" + + # Collapse whitespace so multi-line paragraphs become one clean line. + hook = re.sub(r"\s+", " ", hook).strip() + + # Strip a leading orphan "is" that comes from `**NAME**` + " is …": + # the regex captures the word "is" on its own because the opener is + # typically `**GATEWAY** (known as Tau Ceti...) is the most connected…`. + # Normal reading already works — this is just hygiene. + hook = re.sub(r"\s*\(\s*\)\s*", " ", hook) + + # Hard cap at max_words. Truncate at the last word boundary before + # the cap and append an ellipsis so the reader knows it continues. + words = hook.split() + if len(words) > max_words: + hook = " ".join(words[:max_words]).rstrip(",;:") + "…" + + return hook + + +def main(): + parser = argparse.ArgumentParser( + description="Populate star_systems.gttr_hook from wiki gttr.md files" + ) + parser.add_argument("--db", default=str(DB_PATH), help="Path to systems.db") + parser.add_argument( + "--system", + help="Process only this system_id (e.g. 'GJ 71')", + ) + parser.add_argument( + "--max-words", + type=int, + default=45, + help="Soft cap on hook length (default: 45 words)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Extract and print but do not write to the DB", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Print every extracted hook", + ) + args = parser.parse_args() + + db_path = Path(args.db) + if not db_path.exists(): + print(f"error: {db_path} not found", file=sys.stderr) + sys.exit(1) + + conn = sqlite3.connect(str(db_path), timeout=30.0) + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA busy_timeout=15000") + conn.execute("PRAGMA foreign_keys=ON") + + # Ensure the column exists — idempotent ADD COLUMN for old DBs. + try: + conn.execute("ALTER TABLE star_systems ADD COLUMN gttr_hook TEXT") + except sqlite3.OperationalError: + pass # column already exists + + print(f"\n populate_gttr_hook.py") + print(f" DB: {db_path}") + print(f" max words: {args.max_words}") + if args.dry_run: + print(f" Mode: DRY RUN") + print() + + rows = conn.execute( + "SELECT system_id, proper_name FROM star_systems ORDER BY system_id" + ).fetchall() + if args.system: + rows = [r for r in rows if r[0] == args.system] + + updated = 0 + missing = 0 + unmatched = 0 + + conn.execute("BEGIN") + try: + for system_id, proper_name in rows: + slug = system_id.replace(" ", "-", 1) + gttr_path = WIKI_SYSTEMS / slug / "gttr.md" + + hook = extract_hook(gttr_path, args.max_words) + if hook is None: + if not gttr_path.exists(): + missing += 1 + if args.verbose: + print(f" MISSING {system_id:10s} {gttr_path}") + else: + unmatched += 1 + if args.verbose: + print(f" UNMATCHED {system_id:10s} {gttr_path}") + continue + + if args.verbose: + display = f"{system_id} ({proper_name})" if proper_name else system_id + print(f" {display}") + print(f" → {hook}") + + if not args.dry_run: + conn.execute( + "UPDATE star_systems SET gttr_hook = ? WHERE system_id = ?", + (hook, system_id), + ) + updated += 1 + + if args.dry_run: + conn.rollback() + else: + conn.commit() + except BaseException: + conn.rollback() + conn.close() + raise + + conn.close() + + print(f"\n Done:") + print(f" updated: {updated}") + print(f" missing: {missing}") + print(f" unmatched: {unmatched}") + if args.dry_run: + print(f"\n Dry run — no DB writes.") + print() + + +if __name__ == "__main__": + main() diff --git a/tooling/planet-gen/gemma_naming.py b/tooling/planet-gen/gemma_naming.py index 3a7d43df4..6855e3933 100755 --- a/tooling/planet-gen/gemma_naming.py +++ b/tooling/planet-gen/gemma_naming.py @@ -137,44 +137,66 @@ class Logger: # source of corridor identity in systems.db (cultural_corridor is a # legacy field that was never populated beyond sol-gateway-axis). CORRIDOR_PALETTES: dict[str, dict[str, str]] = { + # IMPORTANT: the inflection is a DOMINANT bias, not a hard lock. + # Earlier iterations used single-culture labels like + # "administrative English" or "Korean/Japanese/Taiwanese" which + # Gemma 2 2B interpreted as "produce ONLY in this register" — and + # every core body came out anglophone, every east_reach body came + # out East Asian, etc. The real Earth diaspora in the setting is + # cosmopolitan: a British surveyor on an east_reach moon still + # names a river after their aunt in Dorset. The inflections now + # explicitly name the dominant register AND invite diaspora + # variety so Gemma samples from the full cross-cultural few-shot + # pool instead of collapsing to the dominant label. "core": { - # NOTE the style label is deliberately plain: earlier versions used - # "institutional Latin / pan-Anglo / Gateway-era" which biased - # Gemma 2 2B toward Latinate coinages like "Aureus" / "Aetheria". - # "Administrative English" gets prosaic output that matches the - # settler-named frontier feel the core corridor actually has. - "inflection": "administrative English / Gateway-era", - "examples": "Meridian, Concord, East Ridge, Landing, Old Gate, Foreman's Run", + "inflection": "Gateway-era Earth diaspora, mostly English, " + "all Earth cultures welcome", + "examples": "Meridian, Concord, East Ridge, Landing, Tanaka, " + "Rahman, Okafor, Ribeiro", }, "north_reach": { - "inflection": "British / Australian / Irish", - "examples": "Wolcott, Mildern, Ashbourne, Kiln, Briarfell, Tarndale", + "inflection": "British/Australian/Irish dominant, all Earth " + "cultures welcome", + "examples": "Wolcott, Mildern, Ashbourne, Kiln, Briarfell, " + "Tarndale, Ribeiro, Nakamura, Kowalski", }, "south_reach": { - "inflection": "Portuguese / Swahili / Cape Verdean / Brazilian", - "examples": "Vargas, Inhaca, Monteforte, Serra, Ribeiro, Kilimi", + "inflection": "Portuguese/Swahili/Cape Verdean/Brazilian " + "dominant, all Earth cultures welcome", + "examples": "Vargas, Inhaca, Monteforte, Serra, Ribeiro, " + "Kilimi, Holmberg, Fairview, Tanaka", }, "east_reach": { - "inflection": "Korean / Japanese / Taiwanese", - "examples": "Hanyang, Takamine, Seoraksan, Tsukuri, Ginoza, Baektu", + "inflection": "Korean/Japanese/Taiwanese dominant, all Earth " + "cultures welcome", + "examples": "Hanyang, Takamine, Seoraksan, Tsukuri, Ginoza, " + "Baektu, Kellogg, Oliveira, Novak", }, "west_reach": { - "inflection": "German / Dutch / Nordic", - "examples": "Vanebach, Kloosterdam, Bergfjord, Hellekade, Straend", + "inflection": "German/Dutch/Nordic dominant, all Earth cultures " + "welcome", + "examples": "Vanebach, Kloosterdam, Bergfjord, Hellekade, " + "Straend, Cooper, Vargas, Nakamura", }, "deep_frontier": { - "inflection": "founder-surname + noun (e.g. 'Okafor Reach', 'Stenner Cross')", - "examples": "Okafor Reach, Stenner Cross, Weller Hold, Pruitt Basin, Vickery Hold", + "inflection": "frontier founder-name era, any Earth culture", + "examples": "Okafor Reach, Stenner Cross, Weller Hold, Pruitt " + "Basin, Vickery Hold, Kellogg Run, Nakamura Post, " + "Ribeiro Landing", }, # Legacy keys retained for backward compatibility with the # cultural_corridor column on the one system that uses it. "sol-gateway-axis": { - "inflection": "administrative English / Gateway-era", - "examples": "Meridian, Concord, East Ridge, Landing, Old Gate, Foreman's Run", + "inflection": "Gateway-era Earth diaspora, mostly English, " + "all Earth cultures welcome", + "examples": "Meridian, Concord, Cardinal, Prefecture, Tanaka, " + "Rahman, Okafor", }, "inner_corridor": { - "inflection": "administrative English", - "examples": "Meridian, Concord, East Ridge, Landing, Old Gate", + "inflection": "Gateway-era Earth diaspora, mostly English, " + "all Earth cultures welcome", + "examples": "Meridian, Concord, East Ridge, Landing, Tanaka, " + "Rahman, Okafor", }, } @@ -657,6 +679,9 @@ def _build_prompt( body_id: str, local_id: str, attempt: int, + system_hook: str | None = None, + system_name: str | None = None, + body_name: str | None = None, ) -> str: """Assemble a few-shot prompt for the given feature type. @@ -664,6 +689,15 @@ def _build_prompt( (body_id, local_id, attempt) — this puts a finger on the sampling scales so neighbouring features on the same body don't all draw from an identical prompt and collapse to identical outputs. + + `system_hook` is the pre-extracted gttr.md characterisation for + this system (e.g. "RAN is, by all accounts, a very nice place to + live, provided you are the sort of person whose grandparents owned + the land"). When present it gets woven into the preamble so Gemma + has per-system cultural context — this is the single biggest lever + for producing names that feel like they belong to THIS world rather + than any world in this corridor. When absent the prompt degrades to + the corridor inflection alone. """ cfg = _PROMPT_CONFIG.get(feature_type) if cfg is None: @@ -684,6 +718,25 @@ def _build_prompt( # singular possessive ones ("their capital town"). Heuristic: if the # subject starts with "their", use "called"; otherwise "named". verb = "called" if subject.startswith("their ") else "named" + + # Context block. When system / body proper names are available, + # label them explicitly so Gemma has the canonical identifiers in + # addition to the gttr hook. Redundancy helps on a small model — + # the hook already opens with the system name but putting it in a + # labeled "System:" line makes it harder to gloss over. When the + # gttr hook exists too, it goes on its own line as the cultural + # one-liner. + context_lines: list[str] = [] + ident_parts = [] + if system_name: + ident_parts.append(f"System: {system_name}") + if body_name: + ident_parts.append(f"Planet: {body_name}") + if ident_parts: + context_lines.append(". ".join(ident_parts) + ".") + if system_hook: + context_lines.append(f"About the system: {system_hook}") + preamble = ( f"Settlers {verb} {subject} after themselves, after what they saw, " f"or after places back home. Most names are mundane, short, and " @@ -694,6 +747,9 @@ def _build_prompt( ) lines = [preamble, ""] + if context_lines: + lines.extend(context_lines) + lines.append("") for style, example in pool: lines.append(f"Style: {style}. Answer: {example}") lines.append("") @@ -1207,6 +1263,7 @@ def name_feature( body_used: set[str], stem_counts: dict[str, int], stem_cap: int, + system_hook: str | None, world_seed: int, body_id: str, local_id: str, @@ -1272,6 +1329,9 @@ def name_feature( body_id=body_id, local_id=local_id, attempt=attempt, + system_hook=system_hook, + system_name=ctx.get("system_proper_name"), + body_name=ctx.get("body_proper_name"), ) try: raw = voice.request(prompt, seed) @@ -1359,6 +1419,26 @@ def load_body_context(body_id: str, system_id: str, conn: sqlite3.Connection) -> } +def load_system_gttr_hooks(conn: sqlite3.Connection) -> dict[str, str]: + """Return `{system_id: gttr_hook}` for every system with a populated + hook. The gttr_hook column is filled by + `tooling/db/populate_gttr_hook.py` from wiki/star-systems//gttr.md + and captures the system's canonical characterisation in 30-45 words + ("where the rules live", "forty years old and still in the draft", + …). The naming pipeline injects this into the prompt preamble so + Gemma has per-system cultural context without having to parse + thousands of lines of wiki markdown at runtime. + + Systems whose gttr_hook is NULL return nothing from this map — the + caller falls back to the corridor palette alone. + """ + rows = conn.execute( + "SELECT system_id, gttr_hook FROM star_systems " + "WHERE gttr_hook IS NOT NULL AND gttr_hook != ''" + ).fetchall() + return {row[0]: row[1] for row in rows} + + def load_body_hop_order(conn: sqlite3.Connection) -> dict[str, tuple[int, str]]: """Return a `{body_id: (hop_distance_from_gateway, body_id)}` map used as a stable sort key so the pipeline walks the reach from core @@ -1416,6 +1496,7 @@ def process_body( corpus: dict[tuple[str, str], set[str]], stem_counts: dict[str, int], stem_cap: int, + system_hook: str | None, world_seed: int, log: "Logger", verbose: bool, @@ -1474,7 +1555,7 @@ def process_body( feature_type = _feature_type_for_city(city) name = name_feature( voice, feature_type, ctx, blocklist, corpus, body_used, - stem_counts, stem_cap, + stem_counts, stem_cap, system_hook, world_seed, body_id, city.get("id") or "city_?", log, verbose, ) @@ -1491,7 +1572,7 @@ def process_body( continue name = name_feature( voice, "river", ctx, blocklist, corpus, body_used, - stem_counts, stem_cap, + stem_counts, stem_cap, system_hook, world_seed, body_id, river.get("id") or "river_?", log, verbose, ) @@ -1511,7 +1592,7 @@ def process_body( feature_type = _feature_type_for_ocean(water) name = name_feature( voice, feature_type, ctx, blocklist, corpus, body_used, - stem_counts, stem_cap, + stem_counts, stem_cap, system_hook, world_seed, body_id, water.get("id") or "water_?", log, verbose, ) @@ -1530,7 +1611,7 @@ def process_body( continue name = name_feature( voice, "mountain_range", ctx, blocklist, corpus, body_used, - stem_counts, stem_cap, + stem_counts, stem_cap, system_hook, world_seed, body_id, rng_feat.get("id") or "range_?", log, verbose, ) @@ -1550,7 +1631,7 @@ def process_body( feature_type = _feature_type_for_poi(poi) name = name_feature( voice, feature_type, ctx, blocklist, corpus, body_used, - stem_counts, stem_cap, + stem_counts, stem_cap, system_hook, world_seed, body_id, poi.get("id") or "poi_?", log, verbose, ) @@ -1725,6 +1806,7 @@ def main(): ensure_atlas_schema(conn) hop_order = load_body_hop_order(conn) + system_gttr_hooks = load_system_gttr_hooks(conn) markers_paths = discover_bodies(args.body, args.limit, hop_order) if not markers_paths: log(f"error: no markers.json found (body={args.body})") @@ -1824,6 +1906,7 @@ def main(): corpus=corpus, stem_counts=stem_counts, stem_cap=args.stem_cap, + system_hook=system_gttr_hooks.get(system_id), world_seed=args.seed, log=log, verbose=args.verbose,