Spaces:
Sleeping
Sleeping
FORGIVING TOWNS: map any building kind (never reject), district reroute, restore god's voice (content-only moderation), town few-shot pushes build_district+roads, honest fail on empty
baf9d60 verified | """GODSEED tool DSL — the single source of truth for the 11-tool surface. | |
| Spec is data (TOOLS); validation is `validate_call`. Semantics per the | |
| architecture contract: | |
| * out-of-range NUMBERS are CLAMPED, never rejected; | |
| * unknown tools / unknown enum values are REJECTED with a terse observation | |
| string the model can react to (e.g. "rejected: unknown kind 'cactus'"); | |
| * missing or non-numeric/malformed args are rejected (they cannot be clamped); | |
| * extra args not in the spec are silently dropped. | |
| `place_water` constraints: kind "stream" needs 2..5 path waypoints and takes | |
| no radius; kind "pool" uses path[0] as its center and requires radius_deg. | |
| `place_road` takes a path of 2..6 [lat,lon] waypoints (a paved surface spline). | |
| Pure stdlib. Other engine modules (and the mind's prompt builder) import this. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from dataclasses import dataclass | |
| from typing import Any, Optional | |
| LAT_RANGE = (-85.0, 85.0) | |
| LON_RANGE = (-180.0, 180.0) | |
| class NumberSpec: | |
| lo: float | |
| hi: float | |
| integer: bool = False | |
| class EnumSpec: | |
| values: tuple[str, ...] | |
| class TextSpec: | |
| max_len: int | |
| class PathSpec: | |
| max_points: int = 5 | |
| FLORA_KINDS = ("trees", "glowgrass", "mushrooms", "vines", "flowers", "reeds") | |
| STRUCTURE_KINDS = ( | |
| "lighthouse", "monolith", "shrine", "village", "beacon", "arch", | |
| "tower", "warehouse", "cafe", "bank", "market", "house", | |
| ) | |
| # The god (a 9B model, no grammar constraint on the live ZeroGPU path) invents | |
| # building names freely — "market_square", "skyscraper", "townhall". REJECTING | |
| # them makes a town wish build nothing and the model gives up (June 12 live: | |
| # "it says it created the town... there is no town"). So place_structure is | |
| # FORGIVING: synonyms map to a real kind, and any unknown kind falls back to a | |
| # house rather than failing. The god always builds something. | |
| STRUCTURE_SYNONYMS: dict[str, str] = { | |
| "market_square": "market", "marketsquare": "market", "square": "market", | |
| "plaza": "market", "bazaar": "market", "marketplace": "market", "stall": "market", | |
| "skyscraper": "tower", "highrise": "tower", "high_rise": "tower", "office": "tower", | |
| "spire": "tower", "obelisk": "monolith", "pillar": "monolith", "stone": "monolith", | |
| "depot": "warehouse", "storehouse": "warehouse", "storage": "warehouse", | |
| "factory": "warehouse", "barn": "warehouse", "silo": "warehouse", | |
| "coffee": "cafe", "coffeehouse": "cafe", "restaurant": "cafe", "diner": "cafe", | |
| "cafeteria": "cafe", "inn": "cafe", "tavern": "cafe", "pub": "cafe", "bakery": "cafe", | |
| "home": "house", "cottage": "house", "cabin": "house", "hut": "house", "lodge": "house", | |
| "dwelling": "house", "residence": "house", "bungalow": "house", "manor": "house", | |
| "church": "shrine", "temple": "shrine", "chapel": "shrine", "cathedral": "shrine", | |
| "townhall": "bank", "town_hall": "bank", "hall": "bank", "courthouse": "bank", | |
| "library": "bank", "museum": "bank", "school": "bank", "guildhall": "bank", | |
| "harbor": "lighthouse", "harbour": "lighthouse", "port": "lighthouse", "dock": "lighthouse", | |
| "gate": "arch", "gateway": "arch", "bridge": "arch", "tower_gate": "arch", | |
| "fire": "beacon", "torch": "beacon", "lantern": "beacon", "lamp": "beacon", | |
| "hamlet": "village", "settlement": "village", "houses": "village", | |
| } | |
| # Kinds that really mean "a whole neighborhood" -> the engine routes them to a | |
| # dense build_district instead of a single building, so the town reads dense. | |
| DISTRICT_SYNONYMS = frozenset(( | |
| "district", "neighborhood", "neighbourhood", "block", "quarter", "town", | |
| "city", "downtown", "suburb", "metropolis", "borough", "ward", | |
| )) | |
| def resolve_structure_kind(value: Any) -> str | None: | |
| """Map a model-supplied kind to a real STRUCTURE_KINDS value. None means | |
| 'this is really a district' (caller should route to build_district). | |
| Unknown-but-buildingish names fall back to 'house' — never a rejection.""" | |
| if not isinstance(value, str): | |
| return "house" | |
| v = value.strip().lower().replace(" ", "_").replace("-", "_") | |
| if v in STRUCTURE_KINDS: | |
| return v | |
| if v in DISTRICT_SYNONYMS: | |
| return None | |
| if v in STRUCTURE_SYNONYMS: | |
| return STRUCTURE_SYNONYMS[v] | |
| # substring rescue (e.g. "grand_market_hall" -> market) before defaulting | |
| for token, kind in STRUCTURE_SYNONYMS.items(): | |
| if token in v: | |
| return kind | |
| for kind in STRUCTURE_KINDS: | |
| if kind in v: | |
| return kind | |
| return "house" # any other word becomes a humble house, never a rejection | |
| WATER_KINDS = ("stream", "pool") | |
| WEATHER_KINDS = ("clear", "rain", "snow", "embers", "mist", "storm") | |
| SKY_PALETTES = ("dawn", "dusk", "night", "aurora", "ember", "void", "gold") | |
| INSCRIBE_STYLES = ("orbit", "stone") | |
| LIFE_KINDS = ("carts", "birds", "fireflies") | |
| TOOLS: dict[str, dict[str, Any]] = { | |
| "raise_terrain": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "radius_deg": NumberSpec(2, 45), | |
| "height": NumberSpec(0.01, 0.12), | |
| "roughness": NumberSpec(0, 1), | |
| }, | |
| "lower_terrain": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "radius_deg": NumberSpec(2, 45), | |
| "depth": NumberSpec(0.01, 0.10), | |
| "roughness": NumberSpec(0, 1), | |
| }, | |
| "spawn_flora": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "radius_deg": NumberSpec(1, 30), | |
| "kind": EnumSpec(FLORA_KINDS), | |
| "density": NumberSpec(0, 1), | |
| "hue": NumberSpec(0, 360), | |
| }, | |
| "place_structure": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "kind": EnumSpec(STRUCTURE_KINDS), | |
| "scale": NumberSpec(0.5, 2.0), | |
| "hue": NumberSpec(0, 360), | |
| }, | |
| "place_water": { | |
| "kind": EnumSpec(WATER_KINDS), | |
| "path": PathSpec(5), | |
| "radius_deg": NumberSpec(1, 10), # pool only | |
| "hue": NumberSpec(160, 260), | |
| }, | |
| "set_weather": { | |
| "kind": EnumSpec(WEATHER_KINDS), | |
| "intensity": NumberSpec(0, 1), | |
| }, | |
| "set_sky": { | |
| "palette": EnumSpec(SKY_PALETTES), | |
| "star_density": NumberSpec(0, 1), | |
| "moons": NumberSpec(0, 3, integer=True), | |
| }, | |
| "inscribe_wish": { | |
| "text": TextSpec(90), | |
| "style": EnumSpec(INSCRIBE_STYLES), | |
| }, | |
| "spawn_life": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "radius_deg": NumberSpec(1, 20), | |
| "kind": EnumSpec(LIFE_KINDS), | |
| "count": NumberSpec(1, 12, integer=True), | |
| "hue": NumberSpec(0, 360), | |
| }, | |
| "build_district": { | |
| "lat": NumberSpec(*LAT_RANGE), | |
| "lon": NumberSpec(*LON_RANGE), | |
| "radius_deg": NumberSpec(2, 15), | |
| "density": NumberSpec(0, 1), | |
| "hue": NumberSpec(0, 360), | |
| }, | |
| "place_road": { | |
| "path": PathSpec(6), # 2..6 [lat,lon] waypoints | |
| }, | |
| } | |
| TOOL_NAMES: tuple[str, ...] = tuple(TOOLS.keys()) | |
| def _snip(value: Any) -> str: | |
| """Terse, single-line render of a bad value for observation strings.""" | |
| s = str(value) | |
| s = "".join(ch if ch.isprintable() else "?" for ch in s) | |
| return s[:32] | |
| def _coerce_number(name: str, value: Any, spec: NumberSpec): | |
| """Returns (clamped_value, None) or (None, rejection).""" | |
| if isinstance(value, bool): | |
| return None, f"rejected: bad arg '{name}'" | |
| if isinstance(value, str): | |
| try: | |
| value = float(value.strip()) | |
| except (ValueError, AttributeError): | |
| return None, f"rejected: bad arg '{name}'" | |
| if not isinstance(value, (int, float)): | |
| return None, f"rejected: bad arg '{name}'" | |
| v = float(value) | |
| if math.isnan(v): | |
| return None, f"rejected: bad arg '{name}'" | |
| v = min(max(v, float(spec.lo)), float(spec.hi)) | |
| if spec.integer: | |
| return int(round(v)), None | |
| return round(v, 4), None | |
| def _coerce_enum(name: str, value: Any, spec: EnumSpec): | |
| if not isinstance(value, str): | |
| return None, f"rejected: unknown {name} '{_snip(value)}'" | |
| v = value.strip().lower() | |
| if v not in spec.values: | |
| return None, f"rejected: unknown {name} '{_snip(value)}'" | |
| return v, None | |
| def _coerce_text(name: str, value: Any, spec: TextSpec): | |
| if not isinstance(value, str): | |
| return None, f"rejected: bad arg '{name}'" | |
| v = "".join(ch for ch in value if ch.isprintable()).strip() | |
| if not v: | |
| return None, f"rejected: empty {name}" | |
| return v[: spec.max_len], None | |
| def _coerce_point(point: Any): | |
| """A waypoint is [lat, lon] (or {'lat':..,'lon':..}); returns [lat, lon] or None.""" | |
| if isinstance(point, dict): | |
| raw = (point.get("lat"), point.get("lon")) | |
| elif isinstance(point, (list, tuple)) and len(point) >= 2: | |
| raw = (point[0], point[1]) | |
| else: | |
| return None | |
| lat, err = _coerce_number("lat", raw[0], NumberSpec(*LAT_RANGE)) | |
| if err: | |
| return None | |
| lon, err = _coerce_number("lon", raw[1], NumberSpec(*LON_RANGE)) | |
| if err: | |
| return None | |
| return [lat, lon] | |
| DEFAULT_WATER_HUE = 190.0 | |
| DEFAULT_POOL_RADIUS = 5.0 | |
| def _validate_place_water(args: dict): | |
| """Models fumble water more than any other tool (June 12 live traces), so | |
| this one is deliberately forgiving: lat/lon synthesize a path, a flat | |
| [lat, lon] pair is wrapped, and hue / pool radius take engine defaults.""" | |
| spec = TOOLS["place_water"] | |
| kind, err = _coerce_enum("kind", args.get("kind"), spec["kind"]) | |
| if err: | |
| return None, err | |
| raw_path = args.get("path") | |
| # lat/lon supplied like every other tool -> a one-point path | |
| if not isinstance(raw_path, (list, tuple)) or not raw_path: | |
| if args.get("lat") is not None and args.get("lon") is not None: | |
| raw_path = [[args.get("lat"), args.get("lon")]] | |
| else: | |
| return None, "rejected: water needs path [[lat,lon],...] or lat+lon" | |
| # a flat [lat, lon] pair instead of [[lat, lon]] | |
| if raw_path and not isinstance(raw_path[0], (list, tuple)): | |
| raw_path = [raw_path] | |
| points = [] | |
| for p in list(raw_path)[: spec["path"].max_points]: # extra waypoints clamped off | |
| pt = _coerce_point(p) | |
| if pt is None: | |
| return None, "rejected: bad waypoint in path (each is [lat, lon])" | |
| points.append(pt) | |
| hue_raw = args.get("hue") | |
| if hue_raw is None: | |
| hue = DEFAULT_WATER_HUE | |
| else: | |
| hue, err = _coerce_number("hue", hue_raw, spec["hue"]) | |
| if err: | |
| return None, err | |
| if kind == "stream": | |
| if len(points) < 2: | |
| return None, "rejected: stream needs 2..5 waypoints" | |
| return {"kind": "stream", "path": points, "hue": hue}, None | |
| # pool: path[0] is the center; radius defaults rather than dies | |
| radius_raw = args.get("radius_deg") | |
| if radius_raw is None: | |
| radius = DEFAULT_POOL_RADIUS | |
| else: | |
| radius, err = _coerce_number("radius_deg", radius_raw, spec["radius_deg"]) | |
| if err: | |
| return None, err | |
| return {"kind": "pool", "path": points[:1], "radius_deg": radius, "hue": hue}, None | |
| def _validate_place_road(args: dict): | |
| """A road is a paved surface spline through 2..6 [lat,lon] waypoints. | |
| Path handling mirrors place_water (a flat [lat, lon] pair is wrapped; extra | |
| waypoints past the max are clamped off) but a road always needs at least two | |
| points — there is no single-point form. | |
| """ | |
| spec = TOOLS["place_road"] | |
| raw_path = args.get("path") | |
| if not isinstance(raw_path, (list, tuple)) or not raw_path: | |
| return None, "rejected: road needs path [[lat,lon],...] (2..6 points)" | |
| # a flat [lat, lon] pair instead of [[lat, lon]] | |
| if not isinstance(raw_path[0], (list, tuple, dict)): | |
| raw_path = [raw_path] | |
| points = [] | |
| for p in list(raw_path)[: spec["path"].max_points]: # extra waypoints clamped off | |
| pt = _coerce_point(p) | |
| if pt is None: | |
| return None, "rejected: bad waypoint in path (each is [lat, lon])" | |
| points.append(pt) | |
| if len(points) < 2: | |
| return None, "rejected: road needs 2..6 waypoints" | |
| return {"path": points}, None | |
| def validate_call(tool: Any, args: Any) -> tuple[Optional[dict], Optional[str]]: | |
| """Validate one tool call. Returns (canonical_args, None) or (None, rejection). | |
| Canonical args contain exactly the spec'd keys (in spec order), with | |
| numbers clamped + rounded, enums lowercased, text sanitized/truncated. | |
| """ | |
| if not isinstance(tool, str) or tool not in TOOLS: | |
| return None, f"rejected: unknown tool '{_snip(tool)}'" | |
| if args is None: | |
| args = {} | |
| if not isinstance(args, dict): | |
| return None, "rejected: args must be an object" | |
| if tool == "place_water": | |
| return _validate_place_water(args) | |
| if tool == "place_road": | |
| return _validate_place_road(args) | |
| # place_structure: forgive the kind. A "district"-ish name becomes a dense | |
| # build_district; any other unknown becomes the nearest real building (or a | |
| # house) — the god never builds nothing because it picked a word we lack. | |
| if tool == "place_structure": | |
| resolved = resolve_structure_kind(args.get("kind")) | |
| if resolved is None: | |
| district_args = { | |
| "lat": args.get("lat"), "lon": args.get("lon"), | |
| "radius_deg": args.get("radius_deg", 6), | |
| "density": args.get("density", 0.7), | |
| "hue": args.get("hue", 40), | |
| } | |
| return validate_call("build_district", district_args) | |
| args = {**args, "kind": resolved} | |
| out: dict[str, Any] = {} | |
| for name, spec in TOOLS[tool].items(): | |
| if name not in args or args.get(name) is None: | |
| return None, f"rejected: missing arg '{name}'" | |
| value = args[name] | |
| if isinstance(spec, NumberSpec): | |
| v, err = _coerce_number(name, value, spec) | |
| elif isinstance(spec, EnumSpec): | |
| v, err = _coerce_enum(name, value, spec) | |
| elif isinstance(spec, TextSpec): | |
| v, err = _coerce_text(name, value, spec) | |
| else: # pragma: no cover — only place_water has PathSpec, handled above | |
| v, err = None, f"rejected: bad arg '{name}'" | |
| if err: | |
| return None, err | |
| out[name] = v | |
| return out, None | |
| # Per-tool minimum path length (place_water's pool form accepts a single | |
| # center point; place_road always needs at least two waypoints). | |
| _PATH_MIN_POINTS: dict[str, int] = {"place_water": 1, "place_road": 2} | |
| def as_dict() -> dict: | |
| """Plain-JSON view of the tool spec (handy for prompts / grammar builders).""" | |
| out: dict[str, dict] = {} | |
| for tool, argspecs in TOOLS.items(): | |
| spec_out: dict[str, dict] = {} | |
| for name, spec in argspecs.items(): | |
| if isinstance(spec, NumberSpec): | |
| spec_out[name] = { | |
| "type": "number", "min": spec.lo, "max": spec.hi, | |
| "integer": spec.integer, | |
| } | |
| elif isinstance(spec, EnumSpec): | |
| spec_out[name] = {"type": "enum", "values": list(spec.values)} | |
| elif isinstance(spec, TextSpec): | |
| spec_out[name] = {"type": "string", "max_len": spec.max_len} | |
| elif isinstance(spec, PathSpec): | |
| spec_out[name] = { | |
| "type": "path", | |
| "min_points": _PATH_MIN_POINTS.get(tool, 1), | |
| "max_points": spec.max_points, | |
| "point": "[lat, lon]", | |
| } | |
| out[tool] = spec_out | |
| out["place_water"]["radius_deg"]["note"] = "pool only" | |
| out["place_water"]["path"]["note"] = "stream: 2..5 waypoints; pool: path[0] is center" | |
| out["place_road"]["path"]["note"] = "2..6 waypoints; a paved road laid on the surface" | |
| return out | |