| from __future__ import annotations |
|
|
| import hashlib |
| import json |
| import os |
| import queue |
| import re |
| import sys |
| import threading |
| import time |
| import traceback |
| import urllib.error |
| import urllib.request |
| from dataclasses import replace |
| from pathlib import Path |
| from typing import Any, Callable |
|
|
| |
| ROOT = Path(__file__).resolve().parent.parent |
| sys.path.insert(0, str(ROOT)) |
|
|
| import torch |
| from fastapi import Request |
| from fastapi.responses import FileResponse, JSONResponse, StreamingResponse |
| from gradio import Server |
|
|
| from config import V2Config |
| from env import FastFloatingBeaconEnv |
| from infer import load_model, planner_mode_for_state |
| from mapgen import configure_motif_mapgen, install_motif_mapgen |
| from replay import plain, write_json |
| from runtime import resolve_device, sync_device |
| from settings import ACTIVE_CHECKPOINT, ENV_DEFAULTS, MAPGEN_KWARGS, PHYSICS_DEFAULTS |
|
|
|
|
| app = Server() |
|
|
|
|
| try: |
| import spaces |
| except Exception: |
| class _SpacesFallback: |
| @staticmethod |
| def GPU(*decorator_args: Any, **_decorator_kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]] | Callable[..., Any]: |
| if decorator_args and callable(decorator_args[0]): |
| return decorator_args[0] |
|
|
| def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: |
| return fn |
|
|
| return decorator |
|
|
| spaces = _SpacesFallback() |
|
|
|
|
| def _load_env_file(path: Path) -> None: |
| try: |
| lines = path.read_text(encoding="utf-8").splitlines() |
| except FileNotFoundError: |
| return |
| except OSError: |
| return |
| for raw_line in lines: |
| line = raw_line.strip() |
| if not line or line.startswith("#") or "=" not in line: |
| continue |
| key, value = line.split("=", 1) |
| key = key.strip() |
| if not key or key in os.environ: |
| continue |
| value = value.strip() |
| if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}: |
| value = value[1:-1] |
| os.environ[key] = value |
|
|
|
|
| _load_env_file(ROOT / ".env") |
|
|
| CHECKPOINT_PATH = ROOT / os.environ.get("PARKOUR_CHECKPOINT", ACTIVE_CHECKPOINT) |
| DEVICE_NAME = os.environ.get("PARKOUR_DEVICE", "auto") |
| TRIAL_DIR = Path(__file__).resolve().parent / "trials" |
| TRIAL_DIR.mkdir(parents=True, exist_ok=True) |
| MAPS_DIR = Path(__file__).resolve().parent / "maps" |
| MAPS_DIR.mkdir(parents=True, exist_ok=True) |
| SAVED_MAPS_DIR = Path(__file__).resolve().parent / "saved_maps" |
| SAVED_MAPS_DIR.mkdir(parents=True, exist_ok=True) |
| SAVED_MAPS_CURATED_DIR = SAVED_MAPS_DIR / "curated" |
| SAVED_MAPS_CURATED_DIR.mkdir(parents=True, exist_ok=True) |
|
|
| APP_MAPGEN_VERSION = "vibe_params_sparse_long_v4" |
| APP_MAPGEN_LLM_LABEL = "LLM vibe knobs" |
|
|
| _lock = threading.Lock() |
| _model_cache: dict[str, Any] = {} |
| _trial_cache: dict[str, dict[str, Any]] = {} |
| _maps_cache: dict[str, dict[str, Any]] = {} |
| _saved_maps_cache: dict[str, dict[str, Any]] = {} |
|
|
|
|
| DIFFICULTIES: dict[str, dict[str, Any]] = { |
| "rookie": { |
| "route_jumps": 6, |
| "distractors": 3, |
| "attempts": 40, |
| "retries": 1, |
| "max_steps_factor": 18.0, |
| "label": "Rookie", |
| }, |
| "standard": { |
| "route_jumps": 9, |
| "distractors": 6, |
| "attempts": 80, |
| "retries": 2, |
| "max_steps_factor": 20.0, |
| "label": "Standard", |
| }, |
| "cursed": { |
| "route_jumps": 15, |
| "distractors": 8, |
| "attempts": 96, |
| "retries": 3, |
| "max_steps_factor": 23.0, |
| "label": "Cursed", |
| }, |
| "nightmare": { |
| "route_jumps": 24, |
| "distractors": 20, |
| "attempts": 96, |
| "retries": 4, |
| "max_steps_factor": 24.0, |
| "label": "Nightmare", |
| }, |
| } |
|
|
| LLM_DIRECT_BASE_SPEC: dict[str, Any] = { |
| "key": "llm_direct", |
| "label": "LLM", |
| "route_jumps": 10, |
| "distractors": 8, |
| "attempts": 128, |
| "retries": 1, |
| } |
|
|
| KNOB_DEFAULTS: dict[str, float] = { |
| "verticality": 0.40, |
| "traps": 0.20, |
| "loopiness": 0.24, |
| "precision": 0.24, |
| "length": 0.42, |
| "jump_gap": 0.40, |
| "platform_size": 0.58, |
| "decoys": 0.16, |
| "elevation_scale": 0.40, |
| "turniness": 0.32, |
| "tiny_platforms": 0.14, |
| "large_platforms": 0.30, |
| "unintuitive": 0.18, |
| } |
|
|
| DIFFICULTY_KNOB_FLOORS: dict[str, dict[str, float]] = { |
| "cursed": { |
| "verticality": 0.76, |
| "traps": 0.84, |
| "loopiness": 0.78, |
| "precision": 0.60, |
| "length": 0.78, |
| "jump_gap": 0.80, |
| "decoys": 0.70, |
| "elevation_scale": 0.78, |
| "turniness": 0.82, |
| "tiny_platforms": 0.58, |
| "unintuitive": 0.84, |
| }, |
| "nightmare": { |
| "verticality": 0.92, |
| "traps": 0.98, |
| "loopiness": 0.94, |
| "precision": 0.76, |
| "length": 0.92, |
| "jump_gap": 0.96, |
| "decoys": 0.90, |
| "elevation_scale": 0.92, |
| "turniness": 0.96, |
| "tiny_platforms": 0.74, |
| "unintuitive": 0.98, |
| }, |
| } |
|
|
| DIFFICULTY_KNOB_CAPS: dict[str, dict[str, float]] = { |
| "rookie": { |
| "traps": 0.34, |
| "loopiness": 0.38, |
| "precision": 0.38, |
| "jump_gap": 0.52, |
| "tiny_platforms": 0.34, |
| "unintuitive": 0.34, |
| }, |
| "standard": { |
| "traps": 0.62, |
| "loopiness": 0.58, |
| "precision": 0.52, |
| "jump_gap": 0.64, |
| "tiny_platforms": 0.48, |
| "unintuitive": 0.54, |
| }, |
| "cursed": { |
| "platform_size": 0.24, |
| "large_platforms": 0.14, |
| }, |
| "nightmare": { |
| "platform_size": 0.12, |
| "large_platforms": 0.06, |
| }, |
| } |
|
|
| def _device() -> torch.device: |
| return resolve_device(DEVICE_NAME) |
|
|
|
|
| def _load_checkpoint() -> tuple[dict[str, Any], Any, torch.nn.Module]: |
| key = f"{CHECKPOINT_PATH}:{_device()}" |
| cached = _model_cache.get(key) |
| if cached is not None: |
| return cached |
| if not CHECKPOINT_PATH.exists(): |
| raise FileNotFoundError(f"checkpoint missing: {CHECKPOINT_PATH}") |
| checkpoint = torch.load(CHECKPOINT_PATH, map_location="cpu", weights_only=False) |
| base_cfg = checkpoint.get("stage_config") or checkpoint["config"] |
| cfg = replace(base_cfg, device=str(_device())) |
| configure_motif_mapgen(**MAPGEN_KWARGS) |
| install_motif_mapgen() |
| model = load_model(checkpoint, cfg, depth=3) |
| model.eval() |
| cached = (checkpoint, cfg, model) |
| _model_cache[key] = cached |
| return cached |
|
|
|
|
| def _difficulty_spec(difficulty: str) -> dict[str, Any]: |
| key = str(difficulty or "standard").strip().lower() |
| return {**DIFFICULTIES.get(key, DIFFICULTIES["standard"]), "key": key if key in DIFFICULTIES else "standard"} |
|
|
|
|
| def _llm_direct_spec(knobs: dict[str, float]) -> dict[str, Any]: |
| length = float(knobs["length"]) |
| decoys = float(knobs["decoys"]) |
| traps = float(knobs["traps"]) |
| precision = float(knobs["precision"]) |
| turniness = float(knobs["turniness"]) |
| jump_gap = float(knobs["jump_gap"]) |
| tiny_platforms = float(knobs["tiny_platforms"]) |
| unintuitive = float(knobs["unintuitive"]) |
| route_jumps = 6 + int(round(length * 3.0 + jump_gap * 1.0 + turniness * 1.0 + precision * 1.0)) |
| distractors = 3 + int(round(decoys * 3.0 + traps * 2.0 + unintuitive * 1.0 + tiny_platforms * 1.0)) |
| attempts = 96 + int(round((length + decoys + traps + jump_gap + unintuitive) * 18.0)) |
| route_jumps = min(route_jumps, 9) |
| distractors = min(distractors, 6) |
| total_platforms = route_jumps + 1 + distractors |
| if total_platforms > 15: |
| distractors = max(2, 15 - (route_jumps + 1)) |
| total_platforms = route_jumps + 1 + distractors |
| if total_platforms > 15: |
| route_jumps = max(5, 15 - distractors - 1) |
| return { |
| **LLM_DIRECT_BASE_SPEC, |
| "route_jumps": int(route_jumps), |
| "distractors": int(distractors), |
| "attempts": min(attempts, 160), |
| } |
|
|
|
|
| def _clamp01(value: float | int | None, default: float) -> float: |
| try: |
| number = float(value) |
| except (TypeError, ValueError): |
| number = float(default) |
| return max(0.0, min(1.0, number)) |
|
|
|
|
| def _knobs( |
| verticality: float | int | None = None, |
| traps: float | int | None = None, |
| loopiness: float | int | None = None, |
| precision: float | int | None = None, |
| length: float | int | None = None, |
| jump_gap: float | int | None = None, |
| platform_size: float | int | None = None, |
| decoys: float | int | None = None, |
| elevation_scale: float | int | None = None, |
| turniness: float | int | None = None, |
| tiny_platforms: float | int | None = None, |
| large_platforms: float | int | None = None, |
| unintuitive: float | int | None = None, |
| ) -> dict[str, float]: |
| return { |
| "verticality": _clamp01(verticality, KNOB_DEFAULTS["verticality"]), |
| "traps": _clamp01(traps, KNOB_DEFAULTS["traps"]), |
| "loopiness": _clamp01(loopiness, KNOB_DEFAULTS["loopiness"]), |
| "precision": _clamp01(precision, KNOB_DEFAULTS["precision"]), |
| "length": _clamp01(length, KNOB_DEFAULTS["length"]), |
| "jump_gap": _clamp01(jump_gap, KNOB_DEFAULTS["jump_gap"]), |
| "platform_size": _clamp01(platform_size, KNOB_DEFAULTS["platform_size"]), |
| "decoys": _clamp01(decoys, KNOB_DEFAULTS["decoys"]), |
| "elevation_scale": _clamp01(elevation_scale, KNOB_DEFAULTS["elevation_scale"]), |
| "turniness": _clamp01(turniness, KNOB_DEFAULTS["turniness"]), |
| "tiny_platforms": _clamp01(tiny_platforms, KNOB_DEFAULTS["tiny_platforms"]), |
| "large_platforms": _clamp01(large_platforms, KNOB_DEFAULTS["large_platforms"]), |
| "unintuitive": _clamp01(unintuitive, KNOB_DEFAULTS["unintuitive"]), |
| } |
|
|
|
|
| def _difficulty_knobs(difficulty_key: str, knobs: dict[str, float]) -> dict[str, float]: |
| key = str(difficulty_key or "standard").strip().lower() |
| tuned = dict(knobs) |
| for name, floor in DIFFICULTY_KNOB_FLOORS.get(key, {}).items(): |
| tuned[name] = max(float(tuned.get(name, 0.0)), float(floor)) |
| for name, cap in DIFFICULTY_KNOB_CAPS.get(key, {}).items(): |
| tuned[name] = min(float(tuned.get(name, 1.0)), float(cap)) |
| return tuned |
|
|
|
|
| def _difficulty_pressure(difficulty_key: str) -> float: |
| return { |
| "rookie": 0.0, |
| "standard": 0.25, |
| "cursed": 0.74, |
| "nightmare": 1.0, |
| }.get(str(difficulty_key or "standard").strip().lower(), 0.25) |
|
|
|
|
| def _direct_vibe(vibe: str, difficulty_key: str, knobs: dict[str, float] | None = None) -> dict[str, Any]: |
| """Convert a free-form vibe string into a vibe plan. |
| |
| The plan's `params` only contains keys the heuristic is willing to set. |
| `_build_vibe_plan` merges it on top of MAPGEN_KWARGS, so the heuristic can |
| push some params higher (or cap them lower for "smooth" vibes) but never |
| silently erase the strong MAPGEN_KWARGS defaults. |
| """ |
| text = str(vibe or "").lower() |
| knobs = knobs or _knobs() |
| params: dict[str, float | int] = {} |
| caps: dict[str, float] = {} |
| tags: list[str] = [] |
| theme = "misty wood ruins" |
| mood = "cool teal fog" |
|
|
| if any(word in text for word in ("vertical", "tower", "climb", "mountain", "summit", "upward", "uphill")): |
| tags.append("vertical") |
| params["map_goal_vertical_bias"] = 0.45 if difficulty_key in ("cursed", "nightmare") else 0.30 |
| params["map_false_summit_fraction"] = max(float(MAPGEN_KWARGS.get("map_false_summit_fraction", 0.14)), 0.08) |
| theme = "vertical ruin stacks" |
| if any(word in text for word in ("trap", "fake", "bait", "cursed", "evil", "dead end", "deceptive")): |
| tags.append("trap-heavy") |
| params["map_trap_jump_fraction"] = max(float(MAPGEN_KWARGS.get("map_trap_jump_fraction", 0.40)), 0.40) |
| params["map_trap_jump_high_fraction"] = max(float(MAPGEN_KWARGS.get("map_trap_jump_high_fraction", 0.20)), 0.20) |
| params["map_greedy_trap_fraction"] = max(float(MAPGEN_KWARGS.get("map_greedy_trap_fraction", 0.22)), 0.22) |
| params["map_false_finish_fraction"] = max(float(MAPGEN_KWARGS.get("map_false_finish_fraction", 0.16)), 0.16) |
| params["map_false_branch_fraction"] = max(float(MAPGEN_KWARGS.get("map_false_branch_fraction", 0.20)), 0.20) |
| mood = "red warning haze" |
| if any(word in text for word in ("loop", "spiral", "maze", "wrong way", "detour", "confusing")): |
| tags.append("loopback") |
| params["map_route_detour_period"] = min(int(MAPGEN_KWARGS.get("map_route_detour_period", 4)), 5) |
| params["map_route_detour_depth"] = max(float(MAPGEN_KWARGS.get("map_route_detour_depth", 0.58)), 0.58) |
| params["map_unintuitive_depth"] = max(float(MAPGEN_KWARGS.get("map_unintuitive_depth", 0.56)), 0.56) |
| params["map_backtrack_period"] = min(int(MAPGEN_KWARGS.get("map_backtrack_period", 5)), 6) |
| params["map_backtrack_depth"] = max(float(MAPGEN_KWARGS.get("map_backtrack_depth", 0.42)), 0.55) |
| params["map_hairpin_period"] = min(int(MAPGEN_KWARGS.get("map_hairpin_period", 7)), 5) |
| params["map_hairpin_depth"] = max(float(MAPGEN_KWARGS.get("map_hairpin_depth", 0.48)), 0.55) |
| theme = "loopback floating garden" |
| if any(word in text for word in ("tiny", "needle", "precision", "small", "narrow")): |
| tags.append("precision") |
| params["map_platform_tiny_fraction"] = max(float(MAPGEN_KWARGS.get("map_platform_tiny_fraction", 0.18)), 0.30) |
| params["map_route_tiny_fraction"] = max(float(MAPGEN_KWARGS.get("map_route_tiny_fraction", 0.10)), 0.18) |
| params["map_platform_min_scale"] = min(float(MAPGEN_KWARGS.get("map_platform_min_scale", 0.52)), 0.46) |
| params["map_edge_gap_min"] = max(float(MAPGEN_KWARGS.get("map_edge_gap_min", 0.95)), 1.05) |
| if any(word in text for word in ("wide", "chill", "easy", "smooth", "flow", "forgiving")): |
| tags.append("flow") |
| caps["map_trap_jump_fraction"] = 0.07 if difficulty_key == "rookie" else 0.10 |
| caps["map_trap_jump_high_fraction"] = 0.03 |
| caps["map_greedy_trap_fraction"] = 0.04 |
| caps["map_false_finish_fraction"] = 0.04 |
| caps["map_false_branch_fraction"] = 0.04 |
| caps["map_route_detour_depth"] = 0.10 |
| caps["map_hairpin_depth"] = 0.10 |
| caps["map_backtrack_depth"] = 0.10 |
| params["map_platform_large_fraction"] = max(float(MAPGEN_KWARGS.get("map_platform_large_fraction", 0.18)), 0.28) |
| params["map_platform_min_scale"] = max(float(MAPGEN_KWARGS.get("map_platform_min_scale", 0.52)), 0.66) |
| mood = "soft open air" |
| if any(word in text for word in ("hairpin", "switchback")): |
| params["map_hairpin_period"] = min(int(MAPGEN_KWARGS.get("map_hairpin_period", 7)), 5) |
| params["map_hairpin_angle"] = max(float(MAPGEN_KWARGS.get("map_hairpin_angle", 1.24)), 1.35) |
| params["map_hairpin_depth"] = max(float(MAPGEN_KWARGS.get("map_hairpin_depth", 0.48)), 0.65) |
| if any(word in text for word in ("backtrack", "backtracking", "return path")): |
| params["map_backtrack_period"] = min(int(MAPGEN_KWARGS.get("map_backtrack_period", 5)), 5) |
| params["map_backtrack_depth"] = max(float(MAPGEN_KWARGS.get("map_backtrack_depth", 0.42)), 0.65) |
| if any(word in text for word in ("valley", "dip", "drop first")): |
| params["map_valley_period"] = min(int(MAPGEN_KWARGS.get("map_valley_period", 5)), 6) |
| params["map_valley_depth"] = max(float(MAPGEN_KWARGS.get("map_valley_depth", 0.58)), 0.70) |
| if any(word in text for word in ("long", "marathon", "extended")): |
| params["map_goal_xy_min"] = max(float(MAPGEN_KWARGS.get("map_goal_xy_min", 28.0)), 40.0) |
| params["map_goal_xy_per_jump_min"] = max(float(MAPGEN_KWARGS.get("map_goal_xy_per_jump_min", 1.90)), 2.6) |
| if any(word in text for word in ("short", "quick", "compact")): |
| params["map_goal_xy_min"] = min(float(MAPGEN_KWARGS.get("map_goal_xy_min", 28.0)), 14.0) |
| params["map_goal_xy_per_jump_min"] = min(float(MAPGEN_KWARGS.get("map_goal_xy_per_jump_min", 1.90)), 1.0) |
|
|
| verticality = knobs["verticality"] |
| traps = knobs["traps"] |
| loopiness = knobs["loopiness"] |
| precision = knobs["precision"] |
| length = knobs["length"] |
| jump_gap = knobs["jump_gap"] |
| platform_size = knobs["platform_size"] |
| decoys = knobs["decoys"] |
| elevation_scale = knobs["elevation_scale"] |
| turniness = knobs["turniness"] |
| tiny_platforms = knobs["tiny_platforms"] |
| large_platforms = knobs["large_platforms"] |
| unintuitive = knobs["unintuitive"] |
|
|
| pressure = _difficulty_pressure(difficulty_key) |
| size_pressure = 1.0 - platform_size |
| params["map_route_gap_scale"] = max(float(params.get("map_route_gap_scale", MAPGEN_KWARGS.get("map_route_gap_scale", 1.0))), 0.92 + 0.58 * jump_gap + 0.20 * pressure) |
| params["map_edge_gap_min"] = max(float(params.get("map_edge_gap_min", MAPGEN_KWARGS.get("map_edge_gap_min", 0.95))), 0.76 + 0.48 * jump_gap + 0.18 * precision + 0.12 * pressure) |
| params["map_edge_gap_max"] = max(float(params.get("map_edge_gap_max", MAPGEN_KWARGS.get("map_edge_gap_max", 1.58))), 1.12 + 0.72 * jump_gap + 0.22 * precision + 0.18 * pressure) |
| params["map_route_edge_gap_floor"] = max(float(params.get("map_route_edge_gap_floor", MAPGEN_KWARGS.get("map_route_edge_gap_floor", 0.38))), 0.34 + 0.22 * pressure) |
| params["map_goal_xy_min"] = max(float(params.get("map_goal_xy_min", MAPGEN_KWARGS.get("map_goal_xy_min", 28.0))), 12.0 + 30.0 * length + 10.0 * jump_gap + 24.0 * pressure) |
| params["map_goal_xy_per_jump_min"] = max(float(params.get("map_goal_xy_per_jump_min", MAPGEN_KWARGS.get("map_goal_xy_per_jump_min", 1.90))), 0.65 + 2.35 * jump_gap + 0.35 * pressure) |
| params["map_direct_decoy_fraction"] = max(float(params.get("map_direct_decoy_fraction", MAPGEN_KWARGS.get("map_direct_decoy_fraction", 0.08))), 0.04 + 0.20 * decoys + 0.08 * pressure) |
| params["map_cluster_extra_fraction"] = max(float(params.get("map_cluster_extra_fraction", MAPGEN_KWARGS.get("map_cluster_extra_fraction", 0.10))), 0.04 + 0.22 * decoys + 0.08 * pressure) |
| params["map_decoy_corridor_fraction"] = max(float(params.get("map_decoy_corridor_fraction", MAPGEN_KWARGS.get("map_decoy_corridor_fraction", 0.06))), 0.03 + 0.20 * decoys + 0.08 * pressure) |
| params["map_false_branch_fraction"] = max(float(params.get("map_false_branch_fraction", MAPGEN_KWARGS.get("map_false_branch_fraction", 0.20))), 0.03 + 0.28 * decoys + 0.14 * unintuitive + 0.14 * pressure) |
| params["map_false_finish_fraction"] = max(float(params.get("map_false_finish_fraction", MAPGEN_KWARGS.get("map_false_finish_fraction", 0.16))), 0.02 + 0.18 * decoys + 0.14 * unintuitive + 0.12 * pressure) |
| params["map_braid_bridge_fraction"] = max(float(params.get("map_braid_bridge_fraction", MAPGEN_KWARGS.get("map_braid_bridge_fraction", 0.16))), 0.04 + 0.24 * turniness + 0.10 * decoys + 0.10 * pressure) |
| params["map_hairpin_period"] = min(int(params.get("map_hairpin_period", MAPGEN_KWARGS.get("map_hairpin_period", 7))), max(3, int(round(9 - 5 * turniness)))) |
| params["map_hairpin_angle"] = max(float(params.get("map_hairpin_angle", MAPGEN_KWARGS.get("map_hairpin_angle", 1.24))), 0.92 + 0.62 * turniness + 0.12 * pressure) |
| params["map_hairpin_depth"] = max(float(params.get("map_hairpin_depth", MAPGEN_KWARGS.get("map_hairpin_depth", 0.48))), 0.16 + 0.64 * turniness + 0.18 * pressure) |
| params["map_route_detour_period"] = min(int(params.get("map_route_detour_period", MAPGEN_KWARGS.get("map_route_detour_period", 4))), max(3, int(round(8 - 4 * turniness)))) |
| params["map_route_detour_depth"] = max(float(params.get("map_route_detour_depth", MAPGEN_KWARGS.get("map_route_detour_depth", 0.58))), 0.14 + 0.48 * turniness + 0.28 * unintuitive + 0.20 * pressure) |
| params["map_unintuitive_depth"] = max(float(params.get("map_unintuitive_depth", MAPGEN_KWARGS.get("map_unintuitive_depth", 0.56))), 0.12 + 0.70 * unintuitive + 0.22 * pressure) |
| params["map_backtrack_depth"] = max(float(params.get("map_backtrack_depth", MAPGEN_KWARGS.get("map_backtrack_depth", 0.42))), 0.08 + 0.56 * unintuitive + 0.22 * pressure) |
| params["map_loopback_anchor_fraction"] = max(float(params.get("map_loopback_anchor_fraction", 0.0)), 0.18 * pressure + 0.22 * unintuitive * pressure) |
| params["map_loopback_anchor_period"] = min(int(params.get("map_loopback_anchor_period", 6)), 4 if pressure > 0.8 else 5) |
| params["map_valley_depth"] = max(float(params.get("map_valley_depth", MAPGEN_KWARGS.get("map_valley_depth", 0.58))), 0.12 + 0.72 * elevation_scale + 0.16 * pressure) |
| params["map_vertical_scale"] = max(float(params.get("map_vertical_scale", 1.70)), 1.15 + 1.05 * elevation_scale + 0.28 * pressure) |
| params["map_max_height"] = max(float(params.get("map_max_height", 7.0)), 5.2 + 3.0 * elevation_scale + 0.8 * pressure) |
| params["map_platform_tiny_fraction"] = max(float(params.get("map_platform_tiny_fraction", MAPGEN_KWARGS.get("map_platform_tiny_fraction", 0.18))), 0.04 + 0.42 * tiny_platforms + 0.10 * size_pressure + 0.10 * pressure) |
| params["map_route_tiny_fraction"] = max(float(params.get("map_route_tiny_fraction", MAPGEN_KWARGS.get("map_route_tiny_fraction", 0.10))), 0.02 + 0.26 * tiny_platforms + 0.10 * precision + 0.14 * pressure) |
| params["map_route_tiny_min_scale"] = min(float(params.get("map_route_tiny_min_scale", 0.44)), 0.44 - 0.08 * pressure) |
| params["map_route_tiny_max_scale"] = min(float(params.get("map_route_tiny_max_scale", 0.68)), 0.68 - 0.10 * pressure) |
| params["map_platform_large_fraction"] = max(float(params.get("map_platform_large_fraction", MAPGEN_KWARGS.get("map_platform_large_fraction", 0.18))), 0.04 + 0.34 * large_platforms) |
| params["map_platform_min_scale"] = min(float(params.get("map_platform_min_scale", MAPGEN_KWARGS.get("map_platform_min_scale", 0.52))), 0.74 - 0.26 * size_pressure - 0.16 * tiny_platforms - 0.06 * pressure) |
| params["map_platform_max_scale"] = max(float(params.get("map_platform_max_scale", MAPGEN_KWARGS.get("map_platform_max_scale", 1.42))), 1.08 + 0.56 * large_platforms + 0.22 * platform_size) |
|
|
| if verticality > 0.05: |
| params["map_goal_vertical_bias"] = max(float(params.get("map_goal_vertical_bias", MAPGEN_KWARGS.get("map_goal_vertical_bias", 0.0))), 0.20 + 0.75 * verticality) |
| params["map_false_summit_fraction"] = max(float(params.get("map_false_summit_fraction", MAPGEN_KWARGS.get("map_false_summit_fraction", 0.14))), 0.06 + 0.14 * verticality) |
| params["map_max_height"] = max(float(params.get("map_max_height", 7.0)), 5.4 + 2.4 * verticality) |
| if "vertical" not in tags and verticality > 0.55: |
| tags.append("vertical") |
| if traps > 0.05: |
| params["map_trap_jump_fraction"] = max(float(params.get("map_trap_jump_fraction", MAPGEN_KWARGS.get("map_trap_jump_fraction", 0.40))), 0.10 + 0.42 * traps) |
| params["map_trap_jump_high_fraction"] = max(float(params.get("map_trap_jump_high_fraction", MAPGEN_KWARGS.get("map_trap_jump_high_fraction", 0.20))), 0.05 + 0.22 * traps) |
| params["map_greedy_trap_fraction"] = max(float(params.get("map_greedy_trap_fraction", MAPGEN_KWARGS.get("map_greedy_trap_fraction", 0.22))), 0.05 + 0.20 * traps) |
| params["map_false_finish_fraction"] = max(float(params.get("map_false_finish_fraction", MAPGEN_KWARGS.get("map_false_finish_fraction", 0.16))), 0.04 + 0.16 * traps) |
| params["map_false_branch_fraction"] = max(float(params.get("map_false_branch_fraction", MAPGEN_KWARGS.get("map_false_branch_fraction", 0.20))), 0.04 + 0.20 * traps) |
| if loopiness > 0.05: |
| params["map_route_detour_depth"] = max(float(params.get("map_route_detour_depth", MAPGEN_KWARGS.get("map_route_detour_depth", 0.58))), 0.18 + 0.50 * loopiness) |
| params["map_unintuitive_depth"] = max(float(params.get("map_unintuitive_depth", MAPGEN_KWARGS.get("map_unintuitive_depth", 0.56))), 0.18 + 0.45 * loopiness) |
| params["map_backtrack_depth"] = max(float(params.get("map_backtrack_depth", MAPGEN_KWARGS.get("map_backtrack_depth", 0.42))), 0.10 + 0.55 * loopiness) |
| params["map_hairpin_depth"] = max(float(params.get("map_hairpin_depth", MAPGEN_KWARGS.get("map_hairpin_depth", 0.48))), 0.10 + 0.55 * loopiness) |
| if loopiness > 0.50: |
| params["map_route_detour_period"] = min(int(params.get("map_route_detour_period", MAPGEN_KWARGS.get("map_route_detour_period", 4))), 5) |
| if "loopback" not in tags: |
| tags.append("loopback") |
| if precision > 0.05: |
| params["map_platform_tiny_fraction"] = max(float(params.get("map_platform_tiny_fraction", MAPGEN_KWARGS.get("map_platform_tiny_fraction", 0.18))), 0.08 + 0.32 * precision) |
| params["map_route_tiny_fraction"] = max(float(params.get("map_route_tiny_fraction", MAPGEN_KWARGS.get("map_route_tiny_fraction", 0.10))), 0.04 + 0.20 * precision) |
| params["map_platform_min_scale"] = min(float(params.get("map_platform_min_scale", MAPGEN_KWARGS.get("map_platform_min_scale", 0.52))), 0.66 - 0.20 * precision) |
| params["map_edge_gap_min"] = max(float(params.get("map_edge_gap_min", MAPGEN_KWARGS.get("map_edge_gap_min", 0.95))), 0.85 + 0.45 * precision) |
| params["map_edge_gap_max"] = max(float(params.get("map_edge_gap_max", MAPGEN_KWARGS.get("map_edge_gap_max", 1.58))), 1.20 + 0.60 * precision) |
| if precision > 0.55 and "precision" not in tags: |
| tags.append("precision") |
|
|
| if difficulty_key == "rookie": |
| caps.setdefault("map_trap_jump_fraction", 0.05) |
| caps.setdefault("map_trap_jump_high_fraction", 0.02) |
| caps.setdefault("map_greedy_trap_fraction", 0.04) |
| caps.setdefault("map_false_branch_fraction", 0.06) |
| caps.setdefault("map_false_finish_fraction", 0.04) |
| caps.setdefault("map_false_summit_fraction", 0.06) |
| caps.setdefault("map_braid_bridge_fraction", 0.08) |
| caps.setdefault("map_route_detour_depth", 0.18) |
| caps.setdefault("map_backtrack_depth", 0.10) |
| caps.setdefault("map_hairpin_depth", 0.18) |
| caps.setdefault("map_unintuitive_depth", 0.18) |
| caps.setdefault("map_loopback_anchor_fraction", 0.0) |
| elif difficulty_key == "standard": |
| caps.setdefault("map_trap_jump_fraction", 0.22) |
| caps.setdefault("map_trap_jump_high_fraction", 0.08) |
| caps.setdefault("map_greedy_trap_fraction", 0.12) |
| caps.setdefault("map_false_branch_fraction", 0.16) |
| caps.setdefault("map_false_finish_fraction", 0.10) |
| caps.setdefault("map_false_summit_fraction", 0.12) |
| caps.setdefault("map_braid_bridge_fraction", 0.16) |
| caps.setdefault("map_route_detour_depth", 0.44) |
| caps.setdefault("map_backtrack_depth", 0.30) |
| caps.setdefault("map_hairpin_depth", 0.40) |
| caps.setdefault("map_unintuitive_depth", 0.42) |
| caps.setdefault("map_loopback_anchor_fraction", 0.08) |
| elif difficulty_key == "cursed": |
| params["map_route_gap_scale"] = max(float(params.get("map_route_gap_scale", 1.0)), 1.24) |
| params["map_edge_gap_min"] = max(float(params.get("map_edge_gap_min", 0.95)), 1.00) |
| params["map_edge_gap_max"] = max(float(params.get("map_edge_gap_max", 1.58)), 1.78) |
| params["map_route_edge_gap_floor"] = max(float(params.get("map_route_edge_gap_floor", 0.38)), 0.44) |
| params["map_goal_xy_min"] = max(float(params.get("map_goal_xy_min", 28.0)), 38.0) |
| params["map_goal_xy_per_jump_min"] = max(float(params.get("map_goal_xy_per_jump_min", 1.90)), 1.92) |
| params["map_goal_vertical_bias"] = max(float(params.get("map_goal_vertical_bias", 0.0)), 0.66) |
| params["map_vertical_scale"] = max(float(params.get("map_vertical_scale", 1.70)), 1.86) |
| params["map_max_height"] = max(float(params.get("map_max_height", 7.0)), 7.4) |
| params["map_platform_min_scale"] = max(float(params.get("map_platform_min_scale", 0.52)), 0.52) |
| params["map_platform_max_scale"] = min(float(params.get("map_platform_max_scale", 1.42)), 1.28) |
| params["map_platform_tiny_fraction"] = max(float(params.get("map_platform_tiny_fraction", 0.18)), 0.18) |
| params["map_route_tiny_fraction"] = max(float(params.get("map_route_tiny_fraction", 0.10)), 0.12) |
| params["map_trap_jump_fraction"] = max(float(params.get("map_trap_jump_fraction", 0.40)), 0.18) |
| params["map_trap_jump_high_fraction"] = max(float(params.get("map_trap_jump_high_fraction", 0.20)), 0.10) |
| params["map_greedy_trap_fraction"] = max(float(params.get("map_greedy_trap_fraction", 0.22)), 0.10) |
| params["map_false_branch_fraction"] = max(float(params.get("map_false_branch_fraction", 0.20)), 0.14) |
| params["map_false_finish_fraction"] = max(float(params.get("map_false_finish_fraction", 0.16)), 0.10) |
| params["map_false_summit_fraction"] = max(float(params.get("map_false_summit_fraction", 0.14)), 0.16) |
| params["map_braid_bridge_fraction"] = max(float(params.get("map_braid_bridge_fraction", 0.16)), 0.14) |
| params["map_direct_decoy_fraction"] = max(float(params.get("map_direct_decoy_fraction", 0.08)), 0.06) |
| params["map_cluster_extra_fraction"] = max(float(params.get("map_cluster_extra_fraction", 0.10)), 0.06) |
| params["map_decoy_corridor_fraction"] = max(float(params.get("map_decoy_corridor_fraction", 0.06)), 0.04) |
| params["map_hairpin_period"] = min(int(params.get("map_hairpin_period", 7)), 3) |
| params["map_hairpin_angle"] = max(float(params.get("map_hairpin_angle", 1.24)), 1.34) |
| params["map_hairpin_depth"] = max(float(params.get("map_hairpin_depth", 0.48)), 0.82) |
| params["map_route_detour_period"] = min(int(params.get("map_route_detour_period", 4)), 3) |
| params["map_route_detour_depth"] = max(float(params.get("map_route_detour_depth", 0.58)), 0.72) |
| params["map_backtrack_period"] = min(int(params.get("map_backtrack_period", 5)), 4) |
| params["map_backtrack_depth"] = max(float(params.get("map_backtrack_depth", 0.42)), 0.58) |
| params["map_unintuitive_depth"] = max(float(params.get("map_unintuitive_depth", 0.56)), 0.68) |
| params["map_valley_depth"] = max(float(params.get("map_valley_depth", 0.58)), 0.52) |
| params["map_loopback_anchor_fraction"] = max(float(params.get("map_loopback_anchor_fraction", 0.0)), 0.08) |
| params["map_loopback_anchor_period"] = min(int(params.get("map_loopback_anchor_period", 6)), 4) |
| params["map_trap_jump_drop"] = max(float(params.get("map_trap_jump_drop", 0.70)), 0.94) |
| params["map_trap_jump_high_max_up"] = max(float(params.get("map_trap_jump_high_max_up", 0.94)), 1.26) |
| params["map_trap_jump_disguise"] = max(float(params.get("map_trap_jump_disguise", 0.82)), 1.06) |
| caps.setdefault("map_route_gap_scale", 1.30) |
| caps.setdefault("map_edge_gap_min", 1.08) |
| caps.setdefault("map_edge_gap_max", 1.86) |
| caps.setdefault("map_route_edge_gap_floor", 0.48) |
| caps.setdefault("map_goal_xy_min", 40.0) |
| caps.setdefault("map_goal_xy_per_jump_min", 2.00) |
| caps.setdefault("map_goal_vertical_bias", 0.70) |
| caps.setdefault("map_vertical_scale", 1.94) |
| caps.setdefault("map_max_height", 7.8) |
| caps.setdefault("map_platform_tiny_fraction", 0.22) |
| caps.setdefault("map_route_tiny_fraction", 0.14) |
| caps.setdefault("map_trap_jump_fraction", 0.24) |
| caps.setdefault("map_trap_jump_high_fraction", 0.12) |
| caps.setdefault("map_greedy_trap_fraction", 0.14) |
| caps.setdefault("map_false_branch_fraction", 0.18) |
| caps.setdefault("map_false_finish_fraction", 0.12) |
| caps.setdefault("map_false_summit_fraction", 0.18) |
| caps.setdefault("map_braid_bridge_fraction", 0.18) |
| caps.setdefault("map_direct_decoy_fraction", 0.08) |
| caps.setdefault("map_cluster_extra_fraction", 0.08) |
| caps.setdefault("map_decoy_corridor_fraction", 0.06) |
| caps.setdefault("map_hairpin_depth", 0.90) |
| caps.setdefault("map_route_detour_depth", 0.82) |
| caps.setdefault("map_backtrack_depth", 0.66) |
| caps.setdefault("map_unintuitive_depth", 0.76) |
| caps.setdefault("map_valley_depth", 0.60) |
| caps.setdefault("map_loopback_anchor_fraction", 0.10) |
| if "cursed" not in tags: |
| tags.append("cursed") |
| elif difficulty_key == "nightmare": |
| |
| |
| params.update({ |
| "map_scenic_turn_scale": 1.16, |
| "map_scenic_vertical_step": 0.72, |
| "map_route_gap_scale": 1.36, |
| "map_edge_gap_min": 1.06, |
| "map_edge_gap_max": 1.92, |
| "map_route_edge_gap_floor": 0.50, |
| "map_goal_xy_min": 38.0, |
| "map_goal_xy_per_jump_min": 1.92, |
| "map_goal_vertical_bias": 0.72, |
| "map_goal_vertical_bias_min": 0.16, |
| "map_vertical_scale": 2.12, |
| "map_max_height": 9.6, |
| "map_platform_min_scale": 0.50, |
| "map_platform_max_scale": 1.26, |
| "map_platform_tiny_fraction": 0.22, |
| "map_route_tiny_fraction": 0.14, |
| "map_route_tiny_min_scale": 0.44, |
| "map_route_tiny_max_scale": 0.68, |
| "map_trap_jump_fraction": 0.22, |
| "map_trap_jump_high_fraction": 0.16, |
| "map_greedy_trap_fraction": 0.14, |
| "map_false_branch_fraction": 0.18, |
| "map_false_finish_fraction": 0.12, |
| "map_false_summit_fraction": 0.20, |
| "map_braid_bridge_fraction": 0.18, |
| "map_direct_decoy_fraction": 0.06, |
| "map_cluster_extra_fraction": 0.08, |
| "map_decoy_corridor_fraction": 0.06, |
| "map_hairpin_period": 3, |
| "map_hairpin_angle": 1.36, |
| "map_hairpin_depth": 1.20, |
| "map_route_detour_period": 3, |
| "map_route_detour_depth": 1.10, |
| "map_backtrack_period": 5, |
| "map_backtrack_depth": 1.00, |
| "map_unintuitive_depth": 1.10, |
| "map_valley_period": 5, |
| "map_valley_depth": 0.36, |
| "map_loopback_anchor_fraction": 0.10, |
| "map_loopback_anchor_period": 4, |
| "map_loopback_turn_gate": 0.26, |
| "map_trap_jump_drop": 0.70, |
| "map_trap_jump_high_max_up": 0.94, |
| "map_trap_jump_disguise": 0.82, |
| }) |
| if "nightmare" not in tags: |
| tags.append("nightmare") |
|
|
| if difficulty_key == "rookie": |
| params.update({ |
| "map_scenic_turn_scale": 0.70, |
| "map_route_gap_scale": 0.96, |
| "map_edge_gap_min": 0.70, |
| "map_edge_gap_max": 1.28, |
| "map_route_edge_gap_floor": 0.26, |
| "map_goal_xy_min": 14.0, |
| "map_goal_xy_per_jump_min": 1.08, |
| "map_goal_vertical_bias": 0.24, |
| "map_vertical_scale": 1.34, |
| "map_max_height": 5.4, |
| "map_platform_min_scale": 0.68, |
| "map_platform_max_scale": 1.58, |
| "map_platform_tiny_fraction": 0.04, |
| "map_route_tiny_fraction": 0.02, |
| "map_trap_jump_fraction": 0.02, |
| "map_trap_jump_high_fraction": 0.0, |
| "map_greedy_trap_fraction": 0.02, |
| "map_false_branch_fraction": 0.02, |
| "map_false_finish_fraction": 0.02, |
| "map_false_summit_fraction": 0.02, |
| "map_braid_bridge_fraction": 0.05, |
| "map_direct_decoy_fraction": 0.02, |
| "map_cluster_extra_fraction": 0.04, |
| "map_decoy_corridor_fraction": 0.02, |
| "map_hairpin_depth": 0.16, |
| "map_route_detour_depth": 0.14, |
| "map_backtrack_depth": 0.06, |
| "map_unintuitive_depth": 0.10, |
| "map_valley_depth": 0.22, |
| "map_loopback_anchor_fraction": 0.0, |
| }) |
| elif difficulty_key == "standard": |
| params.update({ |
| "map_scenic_turn_scale": 0.82, |
| "map_route_gap_scale": 1.04, |
| "map_edge_gap_min": 0.76, |
| "map_edge_gap_max": 1.42, |
| "map_route_edge_gap_floor": 0.30, |
| "map_goal_xy_min": 18.0, |
| "map_goal_xy_per_jump_min": 1.28, |
| "map_goal_vertical_bias": 0.38, |
| "map_vertical_scale": 1.56, |
| "map_max_height": 6.4, |
| "map_platform_min_scale": 0.58, |
| "map_platform_max_scale": 1.50, |
| "map_platform_tiny_fraction": 0.10, |
| "map_route_tiny_fraction": 0.05, |
| "map_trap_jump_fraction": 0.10, |
| "map_trap_jump_high_fraction": 0.03, |
| "map_greedy_trap_fraction": 0.06, |
| "map_false_branch_fraction": 0.08, |
| "map_false_finish_fraction": 0.05, |
| "map_false_summit_fraction": 0.06, |
| "map_braid_bridge_fraction": 0.10, |
| "map_direct_decoy_fraction": 0.04, |
| "map_cluster_extra_fraction": 0.07, |
| "map_decoy_corridor_fraction": 0.04, |
| "map_hairpin_depth": 0.30, |
| "map_route_detour_depth": 0.26, |
| "map_backtrack_depth": 0.14, |
| "map_unintuitive_depth": 0.22, |
| "map_valley_depth": 0.30, |
| "map_loopback_anchor_fraction": 0.0, |
| }) |
|
|
| |
| for key, cap in caps.items(): |
| if key in params: |
| params[key] = min(float(params[key]), float(cap)) |
| else: |
| params[key] = float(cap) |
|
|
| return { |
| "theme": theme, |
| "mood": mood, |
| "tags": tags or ["balanced"], |
| "knobs": knobs, |
| "params": params, |
| "raw_vibe": str(vibe or "").strip(), |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| DIRECT_PLATFORM_FEATURES = ( |
| "extra", |
| "trap_low", |
| "trap_high", |
| "bridge", |
| "island", |
| ) |
|
|
| DIRECT_LLM_SYSTEM_PROMPT = ( |
| "You design floating-platform parkour maps by writing a JSON list of platforms. " |
| "Return one JSON object with the key 'platforms'. Each platform is an object: " |
| "x (float metres, -20..20), y (float metres, -12..12), z (float metres, 0..6), " |
| "w (float width, 0.45..2.4), d (float depth, 0.45..2.4). " |
| "The platforms array is the path in order: first platform is spawn, last is goal. " |
| "Steps should usually be 0.8..2.5 metres apart in xy, with rises of at most 1.4m " |
| "and drops of at most 3.0m. Adjacent route platforms must be jumpable. " |
| "Keep the output compact. Do not include knobs, theme, mood, tags, notes, " |
| "explanations, labels, markdown, is_route, feature, or extra keys. " |
| "Raw compact JSON only." |
| ) |
|
|
|
|
| def _llm_api_key() -> str: |
| return os.environ.get("MAPGEN_LLM_API_KEY", "").strip() or os.environ.get("OPENROUTER_API_KEY", "").strip() |
|
|
|
|
| def _llm_model() -> str: |
| return os.environ.get("MAPGEN_LLM_MODEL", "google/gemma-4-31b-it") |
|
|
|
|
| def _llm_mode() -> str: |
| return os.environ.get("MAPGEN_LLM", "auto").strip().lower() |
|
|
|
|
| def _llm_timeout() -> float: |
| return float(os.environ.get("MAPGEN_LLM_TIMEOUT", "30.0")) |
|
|
|
|
| def _json_from_text(text: str) -> dict[str, Any]: |
| cleaned = str(text or "").strip() |
| cleaned = re.sub(r"^\s*```(?:json)?\s*", "", cleaned, flags=re.IGNORECASE) |
| cleaned = re.sub(r"\s*```\s*$", "", cleaned) |
| try: |
| value = json.loads(cleaned) |
| except json.JSONDecodeError: |
| match = re.search(r"\{.*\}", cleaned, flags=re.DOTALL) |
| if not match: |
| raise |
| candidate = match.group(0) |
| repaired = _repair_json_text(candidate) |
| try: |
| value = json.loads(repaired) |
| except json.JSONDecodeError: |
| value = _extract_platforms_object(repaired) |
| if not isinstance(value, dict): |
| raise ValueError("LLM response did not parse as a JSON object") |
| return value |
|
|
|
|
| def _repair_json_text(text: str) -> str: |
| repaired = str(text or "").strip() |
| repaired = re.sub(r",(\s*[}\]])", r"\1", repaired) |
| repaired = re.sub(r"}\s*{", "},{", repaired) |
| return repaired |
|
|
|
|
| def _extract_platforms_object(text: str) -> dict[str, Any]: |
| key_match = re.search(r'"platforms"\s*:\s*\[', text) |
| if not key_match: |
| raise ValueError("LLM response did not include a parseable platforms array") |
| start = key_match.end() |
| depth = 0 |
| object_start: int | None = None |
| snippets: list[str] = [] |
| for index in range(start, len(text)): |
| char = text[index] |
| if char == "{": |
| if depth == 0: |
| object_start = index |
| depth += 1 |
| elif char == "}": |
| if depth > 0: |
| depth -= 1 |
| if depth == 0 and object_start is not None: |
| snippets.append(text[object_start:index + 1]) |
| object_start = None |
| elif char == "]" and depth == 0: |
| break |
| platforms: list[dict[str, Any]] = [] |
| for snippet in snippets: |
| try: |
| value = json.loads(_repair_json_text(snippet)) |
| except json.JSONDecodeError: |
| value = _loose_platform_object(snippet) |
| if isinstance(value, dict): |
| platforms.append(value) |
| if not platforms: |
| raise ValueError("LLM response did not contain any parseable platform objects") |
| return {"platforms": platforms} |
|
|
|
|
| def _loose_platform_object(text: str) -> dict[str, Any] | None: |
| raw = str(text or "") |
|
|
| def find_number(*keys: str) -> float | None: |
| for key in keys: |
| match = re.search(rf'"{re.escape(key)}"\s*:\s*(-?\d+(?:\.\d+)?)', raw) |
| if match: |
| try: |
| return float(match.group(1)) |
| except ValueError: |
| continue |
| return None |
|
|
| x = find_number("x", "forward") |
| y = find_number("y", "side", "lateral") |
| z = find_number("z", "rise", "z_level") |
| w = find_number("w", "width") |
| d = find_number("d", "depth") |
| if None in {x, y, z, w, d}: |
| return None |
|
|
| is_route_match = re.search(r'"is_route"\s*:\s*(true|false)', raw, flags=re.IGNORECASE) |
| feature_match = re.search(r'"feature"\s*:\s*"([^"]+)"', raw) |
| label_match = re.search(r'"label"\s*:\s*"([^"]+)"', raw) |
| return { |
| "x": x, |
| "y": y, |
| "z": z, |
| "w": w, |
| "d": d, |
| "is_route": bool(is_route_match and is_route_match.group(1).lower() == "true"), |
| "feature": feature_match.group(1) if feature_match else "extra", |
| "label": label_match.group(1) if label_match else "", |
| } |
|
|
|
|
| def _clean_text(value: Any, default: str = "", limit: int = 80) -> str: |
| text = re.sub(r"[\x00-\x1f\x7f]+", " ", str(value or "")).strip() |
| text = re.sub(r"\s+", " ", text) |
| return (text or default)[:limit] |
|
|
|
|
| def _openrouter_request(api_key: str, payload: dict[str, Any]) -> urllib.request.Request: |
| return urllib.request.Request( |
| "https://openrouter.ai/api/v1/chat/completions", |
| data=json.dumps(payload).encode("utf-8"), |
| headers={ |
| "Authorization": f"Bearer {api_key}", |
| "Content-Type": "application/json", |
| "HTTP-Referer": os.environ.get("OPENROUTER_SITE_URL", "https://agent-parkour.local"), |
| "X-Title": "agent-parkour-mapgen", |
| }, |
| method="POST", |
| ) |
|
|
|
|
| def _llm_in_use() -> bool: |
| if _llm_mode() in {"off", "false", "0", "none", "disabled"}: |
| return False |
| return bool(_llm_api_key()) |
|
|
|
|
| def _sanitize_direct_platform(raw: Any, *, is_route: bool, index: int) -> dict[str, Any] | None: |
| if not isinstance(raw, dict): |
| return None |
| try: |
| x = float(raw.get("x", raw.get("forward", 0.0))) |
| y = float(raw.get("y", raw.get("side", raw.get("lateral", 0.0)))) |
| z = float(raw.get("z", raw.get("rise", raw.get("z_level", 0.0)))) |
| w = float(raw.get("w", raw.get("width", 1.0))) |
| d = float(raw.get("d", raw.get("depth", 1.0))) |
| except (TypeError, ValueError): |
| return None |
| if not (all(map(lambda v: v == v, (x, y, z, w, d)))): |
| return None |
| if abs(x) > 24.0 or abs(y) > 16.0 or z < -2.0 or z > 8.0: |
| return None |
| w = max(0.45, min(2.4, w)) |
| d = max(0.45, min(2.4, d)) |
| if is_route: |
| if index == 0 or index == -1: |
| w = max(w, 1.6) |
| d = max(d, 1.6) |
| feature = _clean_text(raw.get("feature"), "extra", limit=16).lower().replace(" ", "_") |
| if feature not in DIRECT_PLATFORM_FEATURES: |
| feature = "extra" |
| return { |
| "x": x, |
| "y": y, |
| "z": z, |
| "w": w, |
| "d": d, |
| "is_route": bool(is_route), |
| "feature": feature, |
| "label": _clean_text(raw.get("label"), f"{'route' if is_route else 'extra'} {index}", limit=36), |
| } |
|
|
|
|
| def _platforms_from_llm_response(raw: dict[str, Any], *, route_jumps: int, distractors: int) -> dict[str, Any]: |
| platforms_raw = raw.get("platforms") |
| if not isinstance(platforms_raw, list): |
| return {"ok": False, "reason": "missing platforms list", "platforms": []} |
| route_needed = max(2, int(route_jumps) + 1) |
| route_platforms: list[dict[str, Any]] = [] |
| for raw_item in platforms_raw: |
| if len(route_platforms) >= route_needed: |
| break |
| spec = _sanitize_direct_platform(raw_item, is_route=True, index=len(route_platforms)) |
| if spec is not None: |
| route_platforms.append(spec) |
| if len(route_platforms) < 2: |
| return {"ok": False, "reason": f"only {len(route_platforms)} valid route platforms", "platforms": []} |
| if len(route_platforms) < route_needed: |
| return { |
| "ok": False, |
| "reason": f"only {len(route_platforms)} valid route platforms; expected {route_needed}", |
| "platforms": [], |
| } |
| return { |
| "ok": True, |
| "reason": "ok", |
| "route": route_platforms, |
| "extras": [], |
| } |
|
|
|
|
| def _request_direct_platforms_with_llm(vibe: str, difficulty_key: str, route_jumps: int, distractors: int) -> dict[str, Any]: |
| api_key = _llm_api_key() |
| if not api_key: |
| raise RuntimeError("MAPGEN_LLM_API_KEY or OPENROUTER_API_KEY is required for LLM direct platform placement") |
| payload = _direct_platforms_payload(vibe, difficulty_key, route_jumps, distractors, stream=False) |
| request = _openrouter_request(api_key, payload) |
| try: |
| with urllib.request.urlopen(request, timeout=_llm_timeout()) as response: |
| data = json.loads(response.read().decode("utf-8")) |
| except urllib.error.HTTPError as exc: |
| body = exc.read().decode("utf-8", errors="replace") |
| raise RuntimeError(f"OpenRouter HTTP {exc.code}: {_clean_text(body, limit=180)}") from exc |
| content = data["choices"][0]["message"]["content"] |
| return _platforms_from_llm_response( |
| _json_from_text(content), |
| route_jumps=route_jumps, |
| distractors=distractors, |
| ) |
|
|
|
|
| def _direct_platforms_payload( |
| vibe: str, difficulty_key: str, route_jumps: int, distractors: int, *, stream: bool |
| ) -> dict[str, Any]: |
| return { |
| "model": _llm_model(), |
| "temperature": 0.25, |
| "max_tokens": 900, |
| "stream": bool(stream), |
| "messages": [ |
| {"role": "system", "content": DIRECT_LLM_SYSTEM_PROMPT}, |
| { |
| "role": "user", |
| "content": json.dumps( |
| { |
| "user_vibe": str(vibe or "")[:1500], |
| "platforms_needed": int(route_jumps) + 1, |
| "physics": { |
| "agent_max_xy_step_metres": 4.5, |
| "agent_max_rise_metres": 1.4, |
| "agent_max_drop_metres": 3.0, |
| "platform_x_extent": 24.0, |
| "platform_y_extent": 16.0, |
| "platform_z_extent": [0.0, 6.0], |
| }, |
| "instructions": [ |
| "Place platforms in metres, not in grid cells.", |
| "Return only one ordered path of blocks.", |
| "First platform is spawn. Last platform is goal.", |
| "Keep it short and valid: one compact JSON object, no prose, no markdown.", |
| "Use exactly the requested number of platforms.", |
| ], |
| }, |
| separators=(",", ":"), |
| ), |
| }, |
| ], |
| } |
|
|
|
|
| def _request_direct_platforms_with_llm_stream( |
| vibe: str, |
| difficulty_key: str, |
| route_jumps: int, |
| distractors: int, |
| emit: Callable[[dict[str, Any]], None], |
| ) -> dict[str, Any]: |
| """Stream the LLM response. Emits `llm_text_delta` per chunk, then `llm_text_done` with the full text.""" |
| api_key = _llm_api_key() |
| if not api_key: |
| raise RuntimeError("MAPGEN_LLM_API_KEY or OPENROUTER_API_KEY is required for LLM direct platform placement") |
| payload = _direct_platforms_payload(vibe, difficulty_key, route_jumps, distractors, stream=True) |
| request = _openrouter_request(api_key, payload) |
| chunks: list[str] = [] |
| try: |
| with urllib.request.urlopen(request, timeout=_llm_timeout()) as response: |
| for raw_line in response: |
| line = raw_line.decode("utf-8", errors="replace").strip() |
| if not line or line.startswith(":"): |
| continue |
| if line.startswith("data:"): |
| line = line[5:].strip() |
| if line == "[DONE]": |
| break |
| try: |
| packet = json.loads(line) |
| except json.JSONDecodeError: |
| continue |
| choices = packet.get("choices") if isinstance(packet, dict) else None |
| if not choices: |
| continue |
| choice = choices[0] if isinstance(choices[0], dict) else {} |
| delta = "" |
| delta_obj = choice.get("delta") |
| if isinstance(delta_obj, dict): |
| delta = str(delta_obj.get("content") or "") |
| if not delta: |
| message_obj = choice.get("message") |
| if isinstance(message_obj, dict): |
| delta = str(message_obj.get("content") or "") |
| if not delta: |
| continue |
| chunks.append(delta) |
| emit({"type": "llm_text_delta", "delta": delta}) |
| except urllib.error.HTTPError as exc: |
| body = exc.read().decode("utf-8", errors="replace") |
| raise RuntimeError(f"OpenRouter HTTP {exc.code}: {_clean_text(body, limit=180)}") from exc |
| content = "".join(chunks).strip() |
| if not content: |
| raise RuntimeError("OpenRouter returned an empty streamed response") |
| emit({"type": "llm_text_done", "text": content}) |
| return _platforms_from_llm_response( |
| _json_from_text(content), |
| route_jumps=route_jumps, |
| distractors=distractors, |
| ) |
|
|
|
|
| def _apply_direct_platforms(env: FastFloatingBeaconEnv, platforms: dict[str, Any]) -> None: |
| """Set `env.platforms` and `env.sizes` from the LLM-produced list. |
| |
| The route platforms are placed at the start, extras fill the rest. The |
| legalizer (called by the caller) fixes overlaps and unreachability. |
| """ |
| n = int(env.n) |
| route_len = int(env.route_len) |
| p = int(env.p) |
| route_specs = list(platforms.get("route", [])) |
| extra_specs = list(platforms.get("extras", [])) |
| if not route_specs: |
| raise RuntimeError("direct platform path got an empty route") |
|
|
| env.platforms.zero_() |
| env.sizes.fill_(0.0) |
| base_w, base_d = float(env.platform_x), float(env.platform_y) |
| min_h = float(getattr(env.cfg, "map_height_offset", 2.85)) + 0.18 |
| max_h = max(min_h + 0.4, float(getattr(env.cfg, "map_max_height", 5.6))) |
|
|
| for layer in range(route_len): |
| spec = route_specs[min(layer, len(route_specs) - 1)] |
| env.platforms[:, layer, 0] = float(spec.get("x", 0.0)) |
| env.platforms[:, layer, 1] = float(spec.get("y", 0.0)) |
| env.platforms[:, layer, 2] = float(min(max(float(spec.get("z", 0.0)) + min_h, min_h), max_h)) |
| env.sizes[:, layer, 0] = max(0.4, min(2.6, float(spec.get("w", 1.0)))) |
| env.sizes[:, layer, 1] = max(0.4, min(2.6, float(spec.get("d", 1.0)))) |
| env.sizes[:, 0] = torch.maximum(env.sizes[:, 0], torch.tensor([base_w * 1.2, base_d * 1.2], device=env.device)) |
| env.sizes[:, route_len - 1] = torch.maximum( |
| env.sizes[:, route_len - 1], |
| torch.tensor([base_w * 1.15, base_d * 1.15], device=env.device), |
| ) |
|
|
| if extra_specs: |
| for e in range(p - route_len): |
| spec = extra_specs[e % len(extra_specs)] |
| idx = route_len + e |
| feature = str(spec.get("feature", "extra")) |
| anchor_layer = int(spec.get("anchor", e % max(1, route_len - 1))) |
| anchor_layer = max(0, min(route_len - 1, anchor_layer)) |
| env.platforms[:, idx, 0] = float(spec.get("x", 0.0)) |
| env.platforms[:, idx, 1] = float(spec.get("y", 0.0)) |
| rise = float(spec.get("rise", 0.0)) |
| if feature == "trap_high": |
| rise = 1.6 |
| elif feature == "trap_low": |
| rise = -1.4 |
| elif feature == "bridge": |
| rise = 0.0 |
| elif feature == "island": |
| rise = (rise if rise != 0.0 else 0.0) |
| env.platforms[:, idx, 2] = float(min(max(env.platforms[0, anchor_layer, 2].item() + rise, min_h), max_h)) |
| env.sizes[:, idx, 0] = max(0.4, min(2.4, float(spec.get("w", 1.0)))) |
| env.sizes[:, idx, 1] = max(0.4, min(2.4, float(spec.get("d", 1.0)))) |
|
|
| from mapgen import ( |
| enforce_route_edge_gap, |
| enforce_route_reach, |
| relocate_route_colliding_extras, |
| resolve_route_extra_grazes, |
| separate_nonroute_overlaps, |
| separate_route_overlaps, |
| ) |
|
|
| edge_floor = max(0.22, float(getattr(env.cfg, "map_route_edge_gap_floor", 0.24))) |
| separate_route_overlaps(env, iterations=18, clearance=0.10) |
| enforce_route_edge_gap(env, floor=edge_floor, passes=2) |
| enforce_route_reach(env, safety=0.16, passes=4) |
| separate_route_overlaps(env, iterations=14, clearance=0.08) |
| enforce_route_reach(env, safety=0.12, passes=3) |
| if p > route_len: |
| relocate_route_colliding_extras(env, iterations=6, clearance=0.32) |
| separate_nonroute_overlaps(env, iterations=32) |
| resolve_route_extra_grazes(env, iterations=6, clearance=0.08) |
| from mapgen import enforce_extra_any_route_reach |
| enforce_extra_any_route_reach(env, safety=0.02, passes=2) |
| separate_nonroute_overlaps(env, iterations=18) |
|
|
| center = env.platforms[:, :, :2].mean(dim=1, keepdim=True) |
| env.platforms[:, :, :2] -= center |
| env.setup_platform_mechanics(env.env_i) |
| env.reset_state(env.env_i) |
| env.update_dynamic_platforms() |
|
|
|
|
| def _make_env( |
| seed: int = 7, |
| route_jumps: int = 12, |
| distractors: int = 6, |
| device: str = "cpu", |
| *, |
| motif_params: dict[str, Any] | None = None, |
| direct_platforms: dict[str, Any] | None = None, |
| ) -> FastFloatingBeaconEnv: |
| if motif_params is not None: |
| configure_motif_mapgen(replace=True, **motif_params) |
| else: |
| configure_motif_mapgen(**MAPGEN_KWARGS) |
| install_motif_mapgen() |
| cfg = V2Config( |
| seed=seed, |
| device=device, |
| envs=1, |
| route_jumps=route_jumps, |
| distractors=distractors, |
| arena_x=32.0, |
| arena_y=18.0, |
| lava_z=-0.9, |
| agent_radius=0.18, |
| max_speed=float(PHYSICS_DEFAULTS["max_speed"]), |
| jump_velocity=float(PHYSICS_DEFAULTS["jump_velocity"]), |
| gravity=float(PHYSICS_DEFAULTS["gravity"]), |
| accel=float(PHYSICS_DEFAULTS["accel"]), |
| ground_damping=float(PHYSICS_DEFAULTS["ground_damping"]), |
| air_damping=float(PHYSICS_DEFAULTS["air_damping"]), |
| air_control=float(PHYSICS_DEFAULTS["air_control"]), |
| sprint_speed_mult=float(PHYSICS_DEFAULTS["sprint_speed_mult"]), |
| sprint_accel_mult=float(PHYSICS_DEFAULTS["sprint_accel_mult"]), |
| turn_speed=float(PHYSICS_DEFAULTS["turn_speed"]), |
| map_height_offset=2.75, |
| map_vertical_scale=1.25, |
| map_max_height=6.0, |
| sensor_mode="topk", |
| sensor_topk=16, |
| pillar_platforms=False, |
| ) |
| env = FastFloatingBeaconEnv(cfg, envs=1, seed=seed) |
| if direct_platforms is not None: |
| _apply_direct_platforms(env, direct_platforms) |
| return env |
|
|
|
|
| def _stage_config(base_cfg: V2Config, spec: dict[str, Any], seed: int, vibe_plan: dict[str, Any]) -> V2Config: |
| max_steps_factor = float(spec.get("max_steps_factor", getattr(base_cfg, "max_steps_factor", 24.0))) |
| cfg = replace( |
| base_cfg, |
| seed=int(seed), |
| device=str(_device()), |
| eval_envs=int(spec["attempts"]), |
| route_jumps=int(spec["route_jumps"]), |
| distractors=int(spec["distractors"]), |
| max_steps_factor=max_steps_factor, |
| run_dir=str(TRIAL_DIR), |
| sensor_mode="topk", |
| sensor_topk=16, |
| sensor_token_range=9.5, |
| pillar_platforms=False, |
| **{key: value for key, value in ENV_DEFAULTS.items() if hasattr(base_cfg, key)}, |
| ) |
| for key, value in vibe_plan["params"].items(): |
| if hasattr(cfg, key): |
| cfg = replace(cfg, **{key: value}) |
| return cfg |
|
|
|
|
| def _sync_for_timing(device: torch.device) -> None: |
| try: |
| sync_device(device) |
| except Exception: |
| pass |
|
|
|
|
| def _runtime_report(model: torch.nn.Module | None = None) -> dict[str, Any]: |
| resolved = _device() |
| model_device = str(next(model.parameters()).device) if model is not None else "" |
| return { |
| "requested_device": DEVICE_NAME, |
| "resolved_device": str(resolved), |
| "model_device": model_device, |
| "uses_mps": model_device.startswith("mps") or (not model_device and resolved.type == "mps"), |
| "uses_cuda": model_device.startswith("cuda") or (not model_device and resolved.type == "cuda"), |
| "mps_available": bool(hasattr(torch.backends, "mps") and torch.backends.mps.is_available()), |
| "cuda_available": bool(torch.cuda.is_available()), |
| "checkpoint": str(CHECKPOINT_PATH.relative_to(ROOT)) if CHECKPOINT_PATH.is_relative_to(ROOT) else str(CHECKPOINT_PATH), |
| } |
|
|
|
|
| def _physics_parity_report(cfg: V2Config) -> dict[str, Any]: |
| keys = tuple(PHYSICS_DEFAULTS.keys()) |
| actual = {key: float(getattr(cfg, key)) for key in keys if hasattr(cfg, key)} |
| expected = {key: float(PHYSICS_DEFAULTS[key]) for key in actual} |
| mismatches = { |
| key: {"actual": actual[key], "expected": expected[key]} |
| for key in actual |
| if abs(actual[key] - expected[key]) > 1.0e-6 |
| } |
| return { |
| "bot_env_matches_shared_defaults": not mismatches, |
| "bot_env": "canonical Python FastFloatingBeaconEnv", |
| "human_try_mode": "browser game-feel physics port; not used for bot certification", |
| "actual": actual, |
| "expected": expected, |
| "mismatches": mismatches, |
| } |
|
|
|
|
| def _capture_eval_state(env: FastFloatingBeaconEnv) -> dict[str, torch.Tensor]: |
| return { |
| "pos": env.pos.detach().clone(), |
| "yaw": env.yaw.detach().clone(), |
| "grounded": env.grounded.detach().clone(), |
| "done": env.done.detach().clone(), |
| "success": env.success.detach().clone(), |
| "steps": env.steps.detach().clone(), |
| "current": env.current.detach().clone(), |
| "visited": env.visited.detach().clone(), |
| "available": env.platform_available.detach().clone(), |
| "progress": env.progress_tensor().detach().clone(), |
| } |
|
|
|
|
| @torch.no_grad() |
| def _evaluate_trial_fast( |
| model: torch.nn.Module, |
| cfg: V2Config, |
| *, |
| setup_fn: Callable[[FastFloatingBeaconEnv], None] | None = None, |
| ) -> tuple[dict[str, float], list[dict[str, Any]], bool, int, dict[str, Any]]: |
| device = next(model.parameters()).device |
| total_start = time.perf_counter() |
| env = FastFloatingBeaconEnv(cfg, envs=int(cfg.eval_envs), seed=int(cfg.seed) + 300_000) |
| if setup_fn is not None: |
| setup_fn(env) |
| obs = env.observe().to(device=device, dtype=torch.float32) |
| else: |
| obs = env.reset().to(device=device, dtype=torch.float32) |
| platforms = env.platforms.detach().clone() |
| sizes = env.sizes.detach().clone() |
| lever_mask = env.lever_mask.detach().clone() |
| locked_by = env.locked_by.detach().clone() |
| fragile = env.fragile_mask.detach().clone() |
| launch_pad = env.launch_pad_mask.detach().clone() |
| moving = env.moving_mask.detach().clone() |
| platform_velocity = env.platform_velocity.detach().clone() |
| _sync_for_timing(device) |
| env_init_seconds = time.perf_counter() - total_start |
|
|
| history: list[dict[str, torch.Tensor]] = [_capture_eval_state(env)] |
| episode_done = torch.zeros(env.n, dtype=torch.bool, device=device) |
| success_seen = torch.zeros(env.n, dtype=torch.bool, device=device) |
| rollout_start = time.perf_counter() |
| for _ in range(env.max_steps): |
| action, _out = model.choose_action(obs, deterministic_controls=True, deterministic_buttons=True) |
| step = env.step(action) |
| done = step.done.to(device=device) |
| success_seen |= (~episode_done) & step.info["success"].to(device=device) |
| episode_done |= done |
| obs = step.observation.to(device=device, dtype=torch.float32) |
| history.append(_capture_eval_state(env)) |
| if bool(episode_done.all().detach().cpu()): |
| break |
| _sync_for_timing(device) |
| rollout_seconds = time.perf_counter() - rollout_start |
|
|
| progress = env.progress_tensor().detach() |
| steps = env.steps.detach() |
| if bool(success_seen.any().detach().cpu()): |
| score = torch.where(success_seen, -steps.float(), torch.full_like(steps.float(), -1.0e9)) |
| else: |
| score = progress * 1000.0 + steps.float() * 1.0e-4 |
| selected = int(score.argmax().detach().cpu()) |
| selected_success = bool(success_seen[selected].detach().cpu()) |
|
|
| frame_start = time.perf_counter() |
| frames = _frames_from_eval_history( |
| cfg, |
| env, |
| history, |
| selected, |
| platforms[selected].detach().cpu(), |
| sizes[selected].detach().cpu(), |
| lever_mask[selected].detach().cpu(), |
| locked_by[selected].detach().cpu(), |
| fragile[selected].detach().cpu(), |
| launch_pad[selected].detach().cpu(), |
| moving[selected].detach().cpu(), |
| platform_velocity[selected].detach().cpu(), |
| ) |
| frame_build_seconds = time.perf_counter() - frame_start |
|
|
| stats = { |
| "eval_success": float(success_seen.float().mean().detach().cpu()), |
| "eval_progress": float(progress.mean().detach().cpu()), |
| "eval_steps": float(steps.float().mean().detach().cpu()), |
| } |
| diagnostics = { |
| "eval_envs": int(env.n), |
| "max_steps": int(env.max_steps), |
| "history_frames": len(history), |
| "selected_attempt": int(selected), |
| "selected_success": bool(selected_success), |
| "env_init_seconds": env_init_seconds, |
| "rollout_seconds": rollout_seconds, |
| "frame_build_seconds": frame_build_seconds, |
| "total_seconds": time.perf_counter() - total_start, |
| "snapshot_strategy": "device tensor history; JSON materialized only for selected rollout", |
| } |
| return stats, frames, selected_success, selected, diagnostics |
|
|
|
|
| def _frames_from_eval_history( |
| cfg: V2Config, |
| env: FastFloatingBeaconEnv, |
| history: list[dict[str, torch.Tensor]], |
| selected: int, |
| platforms: torch.Tensor, |
| sizes: torch.Tensor, |
| lever_mask: torch.Tensor, |
| locked_by: torch.Tensor, |
| fragile: torch.Tensor, |
| launch_pad: torch.Tensor, |
| moving: torch.Tensor, |
| platform_velocity: torch.Tensor, |
| ) -> list[dict[str, Any]]: |
| platforms_list = platforms.tolist() |
| sizes_list = sizes.tolist() |
| lever_mask_list = [bool(x) for x in lever_mask.tolist()] |
| locked_by_list = [int(x) for x in locked_by.tolist()] |
| fragile_list = [bool(x) for x in fragile.tolist()] |
| launch_pad_list = [bool(x) for x in launch_pad.tolist()] |
| moving_list = [bool(x) for x in moving.tolist()] |
| platform_velocity_list = platform_velocity.tolist() |
| goal = int(env.route_len) - 1 |
| frames: list[dict[str, Any]] = [] |
| for row in history: |
| pos = row["pos"][selected].detach().cpu() |
| step = int(row["steps"][selected].detach().cpu()) |
| success = bool(row["success"][selected].detach().cpu()) |
| done = bool(row["done"][selected].detach().cpu()) |
| fallen = bool(float(pos[2]) < float(cfg.lava_z)) |
| timeout = bool(done and (not success) and (not fallen)) |
| available = [bool(x) for x in row["available"][selected].detach().cpu().tolist()] |
| visited = [bool(x) for x in row["visited"][selected].detach().cpu().tolist()] |
| outcome = "success" if success else ("fallen" if fallen else ("timeout" if timeout else "running")) |
| frames.append( |
| { |
| "pos": pos.tolist(), |
| "yaw": float(row["yaw"][selected].detach().cpu()), |
| "stage": float(row["progress"][selected].detach().cpu()) * max(1, goal), |
| "step": step, |
| "grounded": bool(row["grounded"][selected].detach().cpu()), |
| "success": success, |
| "done": done, |
| "fallen": fallen, |
| "burned": False, |
| "timeout": timeout, |
| "outcome": outcome, |
| "landed": int(row["current"][selected].detach().cpu()), |
| "goal": goal, |
| "egoReplay": True, |
| "physicsBackend": "torch_fast_kinematic", |
| "routeMode": "route_free_goal", |
| "plannerMode": "policy_direct_ego_sensor", |
| "sensorMode": str(getattr(cfg, "sensor_mode", "ray")), |
| "platforms": platforms_list, |
| "sizes": sizes_list, |
| "activeNodes": available, |
| "visited": visited, |
| "fireMask": [False for _ in range(env.p)], |
| "fireActive": [False for _ in range(env.p)], |
| "leverMask": lever_mask_list, |
| "leverUnlocked": [False for _ in range(env.p)], |
| "leverProgress": [0 for _ in range(env.p)], |
| "leverHoldSteps": int(max(1, int(getattr(cfg, "unlock_hold_steps", 1)))), |
| "lockedBy": locked_by_list, |
| "fragileMask": fragile_list, |
| "launchPadMask": launch_pad_list, |
| "movingMask": moving_list, |
| "platformVelocity": platform_velocity_list, |
| "platformOpen": available, |
| "platformAvailable": available, |
| "platformSafe": available, |
| } |
| ) |
| if done and step > 0: |
| break |
| return frames |
|
|
|
|
| def _spec_with_knobs(spec: dict[str, Any], knobs: dict[str, float]) -> dict[str, Any]: |
| next_spec = dict(spec) |
| length = float(knobs["length"]) |
| decoys = float(knobs["decoys"]) |
| precision = float(knobs["precision"]) |
| traps = float(knobs["traps"]) |
| loopiness = float(knobs["loopiness"]) |
| jump_gap = float(knobs["jump_gap"]) |
| turniness = float(knobs["turniness"]) |
| tiny_platforms = float(knobs["tiny_platforms"]) |
| unintuitive = float(knobs["unintuitive"]) |
| pressure = _difficulty_pressure(str(next_spec.get("key", "standard"))) |
| key = str(next_spec.get("key", "standard")) |
| if key in {"rookie", "standard"}: |
| extra_jumps = int(round(length * 2.0 + jump_gap * 1.0 + turniness * 0.5 + pressure * 1.0)) |
| extra_distractors = int(round(decoys * 3.0 + traps * 1.0 + unintuitive * 1.0 + pressure * 1.0)) |
| else: |
| extra_jumps = int(round(length * 5.0 + jump_gap * 2.0 + turniness * 1.5 + pressure * 3.0)) |
| extra_distractors = int(round(length * 4.0 + decoys * 8.0 + traps * 3.0 + unintuitive * 4.0 + pressure * 5.0)) |
| next_spec["route_jumps"] = int(next_spec["route_jumps"]) + extra_jumps |
| next_spec["distractors"] = int(next_spec["distractors"]) + extra_distractors |
| next_spec["attempts"] = int(next_spec["attempts"]) + int(round((length + decoys + precision + traps + jump_gap + unintuitive) * 16.0 + pressure * 24.0)) |
| next_spec["retries"] = int(next_spec["retries"]) + int(round((traps + loopiness + precision + jump_gap + unintuitive + tiny_platforms) * 1.3 + pressure * 2.0)) |
| route_caps = {"rookie": 7, "standard": 12, "cursed": 17, "nightmare": 25} |
| distractor_caps = {"rookie": 5, "standard": 8, "cursed": 9, "nightmare": 20} |
| attempt_caps = {"rookie": 56, "standard": 80, "cursed": 96, "nightmare": 96} |
| retry_caps = {"rookie": 1, "standard": 2, "cursed": 3, "nightmare": 4} |
| next_spec["route_jumps"] = min(int(next_spec["route_jumps"]), route_caps.get(key, 16)) |
| next_spec["distractors"] = min(int(next_spec["distractors"]), distractor_caps.get(key, 18)) |
| next_spec["attempts"] = min(int(next_spec["attempts"]), attempt_caps.get(key, 128)) |
| next_spec["retries"] = min(int(next_spec["retries"]), retry_caps.get(key, 6)) |
| return next_spec |
|
|
|
|
| def _build_vibe_plan(vibe: str, difficulty_key: str, knobs: dict[str, float]) -> dict[str, Any]: |
| """Procedural vibe plan: vibe text + UI knobs -> mapgen kwargs. |
| |
| The LLM direct path bypasses this entirely and produces platforms via |
| `_request_direct_platforms_with_llm`. |
| """ |
| return _direct_vibe(vibe, difficulty_key, knobs) |
|
|
|
|
| def _generation_mode_alias(mode: str | None) -> str: |
| """Map UI-facing aliases to the canonical generation mode.""" |
| key = str(mode or "procedural").strip().lower().replace("-", "_").replace(" ", "_") |
| if key in {"", "procedural", "vibe_params", "vibe", "vibes", "vibe_map", "map_vibes", "params", "knobs"}: |
| return "procedural" |
| if key in {"llm_direct", "llm_vibe", "llm", "ai", "direct", "direct_blueprint", "course_architect", "architect", "blueprint"}: |
| return "llm_direct" |
| raise ValueError(f"unknown generation mode: {mode}") |
|
|
|
|
| def _generation_mode_labels() -> dict[str, str]: |
| return { |
| "procedural": "Procedural (knobs)", |
| "llm_direct": "LLM direct (out of distribution)", |
| } |
|
|
|
|
| def _stream_event(event: dict[str, Any]) -> str: |
| return json.dumps(event, default=plain, separators=(",", ":")) + "\n" |
|
|
|
|
| def _certify( |
| difficulty: str, |
| vibe: str, |
| seed: int | None, |
| *, |
| generation_mode: str = "procedural", |
| verticality: float | int | None = None, |
| traps: float | int | None = None, |
| loopiness: float | int | None = None, |
| precision: float | int | None = None, |
| length: float | int | None = None, |
| jump_gap: float | int | None = None, |
| platform_size: float | int | None = None, |
| decoys: float | int | None = None, |
| elevation_scale: float | int | None = None, |
| turniness: float | int | None = None, |
| tiny_platforms: float | int | None = None, |
| large_platforms: float | int | None = None, |
| unintuitive: float | int | None = None, |
| ) -> dict[str, Any]: |
| return _certify_inner( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=generation_mode, |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| direct_platforms=None, |
| ) |
|
|
|
|
| @spaces.GPU(duration=180) |
| def _certify_inner( |
| *, |
| difficulty: str, |
| vibe: str, |
| seed: int | None, |
| generation_mode: str, |
| verticality: float | int | None, |
| traps: float | int | None, |
| loopiness: float | int | None, |
| precision: float | int | None, |
| length: float | int | None, |
| jump_gap: float | int | None, |
| platform_size: float | int | None, |
| decoys: float | int | None, |
| elevation_scale: float | int | None, |
| turniness: float | int | None, |
| tiny_platforms: float | int | None, |
| large_platforms: float | int | None, |
| unintuitive: float | int | None, |
| direct_platforms: dict[str, Any] | None, |
| ) -> dict[str, Any]: |
| load_start = time.perf_counter() |
| checkpoint, base_cfg, model = _load_checkpoint() |
| _sync_for_timing(next(model.parameters()).device) |
| load_seconds = time.perf_counter() - load_start |
| requested_knobs = _knobs( |
| verticality, |
| traps, |
| loopiness, |
| precision, |
| length, |
| jump_gap, |
| platform_size, |
| decoys, |
| elevation_scale, |
| turniness, |
| tiny_platforms, |
| large_platforms, |
| unintuitive, |
| ) |
| mode = _generation_mode_alias(generation_mode) |
| if mode == "llm_direct": |
| knob_values = requested_knobs |
| primary = _llm_direct_spec(knob_values) |
| else: |
| base_spec = _difficulty_spec(difficulty) |
| knob_values = _difficulty_knobs(str(base_spec["key"]), requested_knobs) |
| primary = _spec_with_knobs(base_spec, knob_values) |
| start_seed = int(seed if seed is not None else int.from_bytes(os.urandom(4), "little") % 1_000_000) |
| |
| |
| retry_count = 1 if mode == "llm_direct" else max(1, int(primary.get("retries", 1) or 1)) |
| attempts: list[tuple[dict[str, Any], int]] = [ |
| (primary, start_seed + i * 9973) for i in range(retry_count) |
| ] |
|
|
| attempt_timings: list[dict[str, Any]] = [] |
| candidates: list[dict[str, Any]] = [] |
| best_uncertified: dict[str, Any] | None = None |
| best_stage = 0.0 |
| stop_after_first_certified = True |
|
|
| def remember_uncertified( |
| *, |
| cfg: V2Config, |
| frames: list[dict[str, Any]], |
| stats: dict[str, Any], |
| spec: dict[str, Any], |
| vibe_plan: dict[str, Any], |
| trial_seed: int, |
| selected: int, |
| eval_diag: dict[str, Any], |
| selected_success: bool, |
| extra_diag: dict[str, Any] | None = None, |
| ) -> None: |
| nonlocal best_uncertified |
| if not frames: |
| return |
| merged_diag = dict(eval_diag) |
| if extra_diag: |
| merged_diag.update(extra_diag) |
| fallback_rank = ( |
| 1.0 if selected_success else 0.0, |
| float(_max_stage(frames)), |
| float(stats.get("eval_progress", 0.0) or 0.0), |
| float(stats.get("eval_success", 0.0) or 0.0), |
| float(len(frames)), |
| ) |
| current_rank = best_uncertified["rank"] if best_uncertified is not None else None |
| if current_rank is None or fallback_rank > current_rank: |
| best_uncertified = { |
| "rank": fallback_rank, |
| "checkpoint": checkpoint, |
| "cfg": cfg, |
| "frames": frames, |
| "stats": stats, |
| "difficulty": spec, |
| "vibe_plan": vibe_plan, |
| "seed": int(trial_seed), |
| "selected_attempt": int(selected), |
| "eval_diag": merged_diag, |
| } |
|
|
| for spec, trial_seed in attempts: |
| if mode == "llm_direct": |
| vibe_plan, setup_fn = _setup_llm_direct_attempt( |
| vibe=vibe, |
| difficulty_key=str(spec["key"]), |
| spec=spec, |
| trial_seed=int(trial_seed), |
| direct_platforms=direct_platforms, |
| ) |
| else: |
| vibe_plan = _build_vibe_plan(vibe, str(spec["key"]), knob_values) |
| configure_motif_mapgen(**vibe_plan["params"]) |
| setup_fn = None |
| cfg = _stage_config(base_cfg, spec, trial_seed, vibe_plan) |
| stats, frames, success, selected, eval_diag = _evaluate_trial_fast(model, cfg, setup_fn=setup_fn) |
| attempt_timings.append({ |
| "seed": int(trial_seed), |
| "difficulty": str(spec["key"]), |
| "generation_mode": generation_mode, |
| **eval_diag, |
| }) |
| best_stage = max(best_stage, _max_stage(frames)) |
| if bool(success): |
| first_frame = frames[0] if frames else {} |
| platforms = first_frame.get("platforms", []) if isinstance(first_frame, dict) else [] |
| xy_span, z_span = _platform_span(platforms if isinstance(platforms, list) else []) |
| max_span = _max_layout_span(str(spec["key"])) |
| if xy_span > max_span: |
| layout_diag = { |
| "rejected_reason": "layout_span", |
| "layout_xy_span": float(xy_span), |
| "layout_z_span": float(z_span), |
| "layout_xy_span_limit": float(max_span), |
| } |
| attempt_timings[-1].update(layout_diag) |
| remember_uncertified( |
| cfg=cfg, |
| frames=frames, |
| stats=stats, |
| spec=spec, |
| vibe_plan=vibe_plan, |
| trial_seed=int(trial_seed), |
| selected=int(selected), |
| eval_diag=eval_diag, |
| selected_success=True, |
| extra_diag=layout_diag, |
| ) |
| continue |
| score = _trial_difficulty_score(cfg, frames, stats) |
| candidates.append({ |
| "score": score, |
| "checkpoint": checkpoint, |
| "cfg": cfg, |
| "frames": frames, |
| "stats": stats, |
| "difficulty": spec, |
| "vibe_plan": vibe_plan, |
| "seed": int(trial_seed), |
| "selected_attempt": int(selected), |
| "eval_diag": eval_diag, |
| }) |
| if stop_after_first_certified: |
| break |
| else: |
| remember_uncertified( |
| cfg=cfg, |
| frames=frames, |
| stats=stats, |
| spec=spec, |
| vibe_plan=vibe_plan, |
| trial_seed=int(trial_seed), |
| selected=int(selected), |
| eval_diag=eval_diag, |
| selected_success=False, |
| ) |
| if candidates: |
| winner = max(candidates, key=lambda candidate: float(candidate["score"])) |
| diagnostics = { |
| "runtime": _runtime_report(model), |
| "model_load_seconds": load_seconds, |
| "attempts": attempt_timings, |
| "latest_attempt": winner["eval_diag"], |
| "certified_candidate_count": len(candidates), |
| "selected_seed": int(winner["seed"]), |
| "selection_score": float(winner["score"]), |
| "selection_policy": "highest certified difficulty score across requested-spec seeds", |
| "requested_knobs": requested_knobs, |
| "effective_knobs": knob_values, |
| } |
| payload = _trial_payload( |
| winner["checkpoint"], |
| winner["cfg"], |
| winner["frames"], |
| True, |
| winner["stats"], |
| difficulty=winner["difficulty"], |
| vibe_plan=winner["vibe_plan"], |
| seed=int(winner["seed"]), |
| certified=True, |
| selected_attempt=int(winner["selected_attempt"]), |
| diagnostics=diagnostics, |
| ) |
| return payload |
| if best_uncertified is not None: |
| diagnostics = { |
| "runtime": _runtime_report(model), |
| "model_load_seconds": load_seconds, |
| "attempts": attempt_timings, |
| "latest_attempt": best_uncertified["eval_diag"], |
| "certified_candidate_count": 0, |
| "selected_seed": int(best_uncertified["seed"]), |
| "selection_policy": "highest progress uncertified attempt across requested-spec seeds", |
| "requested_knobs": requested_knobs, |
| "effective_knobs": knob_values, |
| "failure_reason": ( |
| f"certification failed for {primary['key']} after {len(attempt_timings)} attempts; " |
| f"best_stage={best_stage:.2f}" |
| ), |
| } |
| payload = _trial_payload( |
| best_uncertified["checkpoint"], |
| best_uncertified["cfg"], |
| best_uncertified["frames"], |
| False, |
| best_uncertified["stats"], |
| difficulty=best_uncertified["difficulty"], |
| vibe_plan=best_uncertified["vibe_plan"], |
| seed=int(best_uncertified["seed"]), |
| certified=False, |
| selected_attempt=int(best_uncertified["selected_attempt"]), |
| diagnostics=diagnostics, |
| ) |
| return payload |
| raise RuntimeError("certification failed before producing any usable rollout frames") |
|
|
|
|
| def _certify_stream( |
| *, |
| difficulty: str, |
| vibe: str, |
| seed: int | None, |
| generation_mode: str, |
| verticality: float | int | None, |
| traps: float | int | None, |
| loopiness: float | int | None, |
| precision: float | int | None, |
| length: float | int | None, |
| jump_gap: float | int | None, |
| platform_size: float | int | None, |
| decoys: float | int | None, |
| elevation_scale: float | int | None, |
| turniness: float | int | None, |
| tiny_platforms: float | int | None, |
| large_platforms: float | int | None, |
| unintuitive: float | int | None, |
| emit: Callable[[dict[str, Any]], None], |
| ) -> None: |
| """Run _certify_inner and emit each trial payload as a `done` event.""" |
| direct_platforms: dict[str, Any] | None = None |
| if generation_mode == "llm_direct": |
| requested_knobs = _knobs( |
| verticality, |
| traps, |
| loopiness, |
| precision, |
| length, |
| jump_gap, |
| platform_size, |
| decoys, |
| elevation_scale, |
| turniness, |
| tiny_platforms, |
| large_platforms, |
| unintuitive, |
| ) |
| spec = _llm_direct_spec(requested_knobs) |
| emit({ |
| "type": "status", |
| "message": "streaming llm platform json", |
| "generation_mode": generation_mode, |
| "route_jumps": int(spec["route_jumps"]), |
| "distractors": int(spec["distractors"]), |
| }) |
| direct_platforms = _request_direct_platforms_with_llm_stream( |
| vibe, |
| str(spec["key"]), |
| int(spec["route_jumps"]), |
| int(spec["distractors"]), |
| emit, |
| ) |
| emit({"type": "status", "message": "certifying streamed llm map", "generation_mode": generation_mode}) |
|
|
| payload = _certify_inner( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=generation_mode, |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| direct_platforms=direct_platforms, |
| ) |
| emit({"type": "done", "trial": payload}) |
|
|
|
|
| def _setup_llm_direct_attempt( |
| *, |
| vibe: str, |
| difficulty_key: str, |
| spec: dict[str, Any], |
| trial_seed: int, |
| direct_platforms: dict[str, Any] | None = None, |
| ) -> tuple[dict[str, Any], Callable[[FastFloatingBeaconEnv], None]]: |
| """Call the LLM to get a platform list and build a setup callback for the env.""" |
| if not _llm_in_use(): |
| raise RuntimeError("LLM direct path requires MAPGEN_LLM_API_KEY (or OPENROUTER_API_KEY) and MAPGEN_LLM=auto") |
| route_jumps = int(spec["route_jumps"]) |
| distractors = int(spec["distractors"]) |
| if direct_platforms is not None: |
| result = direct_platforms |
| else: |
| result = _request_direct_platforms_with_llm(vibe, difficulty_key, route_jumps, distractors) |
| if not result.get("ok"): |
| raise RuntimeError(f"LLM did not produce a usable platform list: {result.get('reason')}") |
| platforms = result |
|
|
| def setup_fn(env: FastFloatingBeaconEnv) -> None: |
| _apply_direct_platforms(env, platforms) |
|
|
| vibe_plan = { |
| "theme": "llm direct platforms", |
| "mood": "out of distribution", |
| "tags": ["llm-direct", "ood"], |
| "knobs": {}, |
| "params": {}, |
| "raw_vibe": str(vibe or "").strip(), |
| "direct_platforms": { |
| "source": "llm", |
| "model": _llm_model(), |
| "seed": int(trial_seed), |
| "route_count": len(platforms.get("route", [])), |
| "extras_count": len(platforms.get("extras", [])), |
| }, |
| } |
| return vibe_plan, setup_fn |
|
|
|
|
| def _select_rollout(frames_by_env: list[list[dict[str, Any]]], successes: list[bool]) -> int: |
| return max( |
| range(len(frames_by_env)), |
| key=lambda i: ( |
| 1 if bool(successes[i]) else 0, |
| _max_stage(frames_by_env[i]), |
| len(frames_by_env[i]), |
| ), |
| ) |
|
|
|
|
| def _max_stage(frames: list[dict[str, Any]]) -> float: |
| return max((float(frame.get("stage", 0.0) or 0.0) for frame in frames), default=0.0) |
|
|
|
|
| def _platform_span(platforms: list[Any]) -> tuple[float, float]: |
| points: list[tuple[float, float, float]] = [] |
| for platform in platforms: |
| if not isinstance(platform, (list, tuple)) or len(platform) < 3: |
| continue |
| try: |
| points.append((float(platform[0]), float(platform[1]), float(platform[2]))) |
| except (TypeError, ValueError): |
| continue |
| if not points: |
| return 0.0, 0.0 |
| xs = [point[0] for point in points] |
| ys = [point[1] for point in points] |
| zs = [point[2] for point in points] |
| xy_span = ((max(xs) - min(xs)) ** 2 + (max(ys) - min(ys)) ** 2) ** 0.5 |
| return float(xy_span), float(max(zs) - min(zs)) |
|
|
|
|
| def _max_layout_span(difficulty_key: str) -> float: |
| return { |
| "rookie": 80.0, |
| "standard": 110.0, |
| "cursed": 155.0, |
| "nightmare": 190.0, |
| }.get(str(difficulty_key or "standard"), 110.0) |
|
|
|
|
| def _route_turn_score(platforms: list[Any], route_len: int) -> float: |
| if route_len < 3: |
| return 0.0 |
| route = [] |
| for platform in platforms[:route_len]: |
| if not isinstance(platform, (list, tuple)) or len(platform) < 2: |
| continue |
| try: |
| route.append((float(platform[0]), float(platform[1]))) |
| except (TypeError, ValueError): |
| continue |
| if len(route) < 3: |
| return 0.0 |
| total = 0.0 |
| for index in range(1, len(route) - 1): |
| ax = route[index][0] - route[index - 1][0] |
| ay = route[index][1] - route[index - 1][1] |
| bx = route[index + 1][0] - route[index][0] |
| by = route[index + 1][1] - route[index][1] |
| alen = (ax * ax + ay * ay) ** 0.5 |
| blen = (bx * bx + by * by) ** 0.5 |
| if alen <= 1.0e-6 or blen <= 1.0e-6: |
| continue |
| dot = max(-1.0, min(1.0, (ax * bx + ay * by) / (alen * blen))) |
| total += abs(torch.acos(torch.tensor(dot)).item()) |
| return float(total) |
|
|
|
|
| def _trial_difficulty_score(cfg: V2Config, frames: list[dict[str, Any]], stats: dict[str, Any]) -> float: |
| first = frames[0] if frames else {} |
| terminal = frames[-1] if frames else {} |
| platforms = first.get("platforms", []) if isinstance(first, dict) else [] |
| sizes = first.get("sizes", []) if isinstance(first, dict) else [] |
| route_len = int(getattr(cfg, "route_jumps", 1)) + 1 |
| route_jumps = max(1, route_len - 1) |
| distractors = int(getattr(cfg, "distractors", 0)) |
| par_steps = float(terminal.get("step", max(0, len(frames) - 1)) or 0.0) |
| eval_success = max(0.0, min(1.0, float(stats.get("eval_success", 1.0) or 0.0))) |
| eval_progress = max(0.0, min(1.0, float(stats.get("eval_progress", 1.0) or 0.0))) |
| xy_span, z_span = _platform_span(platforms if isinstance(platforms, list) else []) |
| turn_score = _route_turn_score(platforms if isinstance(platforms, list) else [], route_len) |
| tiny_count = 0 |
| if isinstance(sizes, list): |
| for size in sizes[:route_len]: |
| if not isinstance(size, (list, tuple)) or len(size) < 2: |
| continue |
| try: |
| if min(float(size[0]), float(size[1])) < 0.78: |
| tiny_count += 1 |
| except (TypeError, ValueError): |
| continue |
| max_score_span = 190.0 if route_jumps >= 25 else (155.0 if route_jumps >= 20 else 110.0) |
| xy_span = min(xy_span, max_score_span) |
| return float( |
| par_steps * 0.55 |
| + route_jumps * 3.0 |
| + distractors * 1.15 |
| + xy_span * 0.32 |
| + z_span * 2.2 |
| + turn_score * 7.0 |
| + tiny_count * 4.5 |
| + (1.0 - eval_success) * 170.0 |
| + (1.0 - eval_progress) * 35.0 |
| ) |
|
|
|
|
| def _trial_payload( |
| checkpoint: dict[str, Any], |
| cfg: V2Config, |
| frames: list[dict[str, Any]], |
| success: bool, |
| stats: dict[str, Any], |
| *, |
| difficulty: dict[str, Any], |
| vibe_plan: dict[str, Any], |
| seed: int, |
| certified: bool, |
| selected_attempt: int, |
| diagnostics: dict[str, Any] | None = None, |
| ) -> dict[str, Any]: |
| first = frames[0] if frames else {} |
| terminal = frames[-1] if frames else {} |
| dt = float(getattr(cfg, "dt", 0.05)) |
| par_steps = int(terminal.get("step", max(0, len(frames) - 1)) or 0) |
| par_time = par_steps * dt |
| trial_id = _trial_id(seed, difficulty["key"], vibe_plan["raw_vibe"], certified, par_steps) |
| payload = { |
| "trial_id": trial_id, |
| "status": "certified" if certified else "uncertified", |
| "certified": bool(certified), |
| "seed": int(seed), |
| "difficulty": difficulty["key"], |
| "difficulty_label": difficulty["label"], |
| "vibe": vibe_plan["raw_vibe"], |
| "vibe_plan": {key: value for key, value in vibe_plan.items() if key != "params"}, |
| "map": { |
| "platforms": first.get("platforms", []), |
| "sizes": first.get("sizes", []), |
| "goal": first.get("goal", int(getattr(cfg, "route_jumps", 1)) - 1), |
| "routeMode": first.get("routeMode", "route_free_goal"), |
| "theme": vibe_plan["theme"], |
| "mood": vibe_plan["mood"], |
| }, |
| "bot": { |
| "name": "Tiny v37", |
| "success": bool(success), |
| "par_time": float(par_time), |
| "par_steps": int(par_steps), |
| "max_stage": float(_max_stage(frames)), |
| "selected_attempt": int(selected_attempt), |
| "eval_success": float(stats.get("eval_success", 0.0) or 0.0), |
| "eval_progress": float(stats.get("eval_progress", 0.0) or 0.0), |
| "checkpoint": str(CHECKPOINT_PATH.relative_to(ROOT)) if CHECKPOINT_PATH.is_relative_to(ROOT) else str(CHECKPOINT_PATH), |
| "planner_mode": planner_mode_for_state(checkpoint["model"]), |
| }, |
| "replay": { |
| "frames": frames, |
| "frame_count": len(frames), |
| "dt": dt, |
| }, |
| "config": plain(cfg), |
| "debug": { |
| "physics_parity": _physics_parity_report(cfg), |
| "route_observation": "none", |
| "certification": "policy rollout in canonical Python env; user sees recorded successful states as ghost", |
| "trialing": diagnostics or {}, |
| }, |
| } |
| _trial_cache[trial_id] = payload |
| write_json(TRIAL_DIR / f"{trial_id}.json", payload, compact=True) |
| return payload |
|
|
|
|
| def _trial_id(seed: int, difficulty: str, vibe: str, certified: bool, par_steps: int) -> str: |
| digest = hashlib.sha1(f"{seed}:{difficulty}:{vibe}:{certified}:{par_steps}".encode("utf-8")).hexdigest()[:10] |
| return f"trial_{difficulty}_{seed}_{digest}" |
|
|
|
|
| def _load_trial(trial_id: str) -> dict[str, Any] | None: |
| if trial_id in _trial_cache: |
| return _trial_cache[trial_id] |
| path = TRIAL_DIR / f"{trial_id}.json" |
| if not path.exists(): |
| return None |
| import json |
|
|
| payload = json.loads(path.read_text(encoding="utf-8")) |
| _trial_cache[trial_id] = payload |
| return payload |
|
|
|
|
| def _safe_stem(value: Any, *, default: str = "map") -> str: |
| text = re.sub(r"[^a-zA-Z0-9_.-]+", "_", str(value or "").strip()).strip("._-") |
| return text[:96] or default |
|
|
|
|
| def _local_request_only(request: Request) -> None: |
| host = (request.client.host if request.client else "") or "" |
| if host in {"127.0.0.1", "::1", "localhost"}: |
| return |
| raise PermissionError("saving sample maps is local-only") |
|
|
|
|
| def _saved_map_path(saved_id: str) -> Path: |
| return SAVED_MAPS_DIR / f"{_safe_stem(saved_id, default='sample')}.json" |
|
|
|
|
| def _saved_map_curated_path(saved_id: str) -> Path: |
| return SAVED_MAPS_CURATED_DIR / f"{_safe_stem(saved_id, default='sample')}.json" |
|
|
|
|
| def _iter_saved_map_paths() -> list[Path]: |
| return [ |
| *sorted(SAVED_MAPS_CURATED_DIR.glob("*.json")), |
| *sorted(SAVED_MAPS_DIR.glob("*.json")), |
| ] |
|
|
|
|
| def _read_saved_map_file(path: Path) -> dict[str, Any] | None: |
| try: |
| payload = json.loads(path.read_text(encoding="utf-8")) |
| except (OSError, json.JSONDecodeError): |
| return None |
| return payload if isinstance(payload, dict) else None |
|
|
|
|
| def _saved_map_id(trial: dict[str, Any]) -> str: |
| trial_id = str(trial.get("trial_id") or "") |
| difficulty = _safe_stem(trial.get("difficulty") or "sample", default="sample") |
| seed = int(trial.get("seed", 0) or 0) |
| digest = hashlib.sha1(trial_id.encode("utf-8")).hexdigest()[:10] |
| return f"sample_{difficulty}_{seed}_{digest}" |
|
|
|
|
| def _saved_map_name(trial: dict[str, Any], name: str | None) -> str: |
| if name: |
| return _clean_text(name, default="Saved Map", limit=48) |
| difficulty = _clean_text(trial.get("difficulty_label") or trial.get("difficulty"), default="Sample", limit=24) |
| seed = int(trial.get("seed", 0) or 0) |
| theme = _clean_text((trial.get("map") or {}).get("theme"), default="", limit=24) |
| if theme and theme.lower() not in {"floating platforms", "llm direct platforms"}: |
| return _clean_text(f"{difficulty} {theme}", default=f"{difficulty} {seed}", limit=48) |
| return _clean_text(f"{difficulty} {seed}", default="Saved Map", limit=48) |
|
|
|
|
| def _saved_map_metadata(saved: dict[str, Any], *, path: Path | None = None) -> dict[str, Any]: |
| trial = saved.get("trial") if isinstance(saved.get("trial"), dict) else saved |
| trial_map = trial.get("map") if isinstance(trial.get("map"), dict) else {} |
| bot = trial.get("bot") if isinstance(trial.get("bot"), dict) else {} |
| replay = trial.get("replay") if isinstance(trial.get("replay"), dict) else {} |
| platforms = trial_map.get("platforms") if isinstance(trial_map.get("platforms"), list) else [] |
| sizes = trial_map.get("sizes") if isinstance(trial_map.get("sizes"), list) else [] |
| saved_id = str(saved.get("id") or _saved_map_id(trial)) |
| load_id = _safe_stem(path.stem if path is not None else saved_id, default=saved_id or "sample") |
| difficulty = str(trial.get("difficulty") or "standard") |
| par_time = float(bot.get("par_time", 0.0) or 0.0) |
| goal = int(trial_map.get("goal", max(0, len(platforms) - 1)) or 0) |
| return { |
| "id": saved_id, |
| "load_id": load_id, |
| "trial_id": str(trial.get("trial_id") or ""), |
| "name": _clean_text(saved.get("name") or _saved_map_name(trial, None), default=saved_id, limit=48), |
| "difficulty": difficulty, |
| "difficulty_label": trial.get("difficulty_label") or difficulty.title(), |
| "seed": int(trial.get("seed", 0) or 0), |
| "certified": bool(trial.get("certified")), |
| "jumps": max(0, goal), |
| "platform_count": len(platforms), |
| "frame_count": int(replay.get("frame_count", len(replay.get("frames", []) or [])) or 0), |
| "par_time": par_time, |
| "tempo": "fast" if par_time and par_time < 8 else "technical" if par_time and par_time < 18 else "long", |
| "theme": trial_map.get("theme", ""), |
| "mood": trial_map.get("mood", ""), |
| "preview_platforms": platforms, |
| "preview_sizes": sizes, |
| "preview_goal": goal, |
| "preview_config": trial.get("config") if isinstance(trial.get("config"), dict) else {}, |
| "saved_at": int(saved.get("saved_at", 0) or 0), |
| } |
|
|
|
|
| def _save_trial_as_sample(trial_id: str, *, name: str | None = None) -> dict[str, Any]: |
| clean_trial_id = _safe_stem(trial_id, default="") |
| if clean_trial_id != str(trial_id): |
| raise ValueError("invalid trial_id") |
| trial = _load_trial(clean_trial_id) |
| if trial is None: |
| raise FileNotFoundError(f"trial not found: {clean_trial_id}") |
| if not trial.get("replay", {}).get("frames"): |
| raise ValueError("trial has no replay frames") |
| saved_id = _saved_map_id(trial) |
| saved = { |
| "id": saved_id, |
| "trial_id": clean_trial_id, |
| "name": _saved_map_name(trial, name), |
| "saved_at": int(time.time()), |
| "source": "curated", |
| "trial": trial, |
| } |
| path = _saved_map_curated_path(saved_id) |
| write_json(path, saved, compact=True) |
| _saved_maps_cache[saved_id] = saved |
| return { |
| "status": "ok", |
| "saved_map": _saved_map_metadata(saved, path=path), |
| "path": str(path.relative_to(ROOT)), |
| } |
|
|
|
|
| def _load_saved_map(saved_id: str) -> dict[str, Any] | None: |
| clean_id = _safe_stem(saved_id, default="") |
| if not clean_id: |
| return None |
| cached = _saved_maps_cache.get(clean_id) |
| if cached is not None: |
| return cached |
|
|
| for path in (_saved_map_curated_path(clean_id), _saved_map_path(clean_id)): |
| if not path.exists(): |
| continue |
| payload = _read_saved_map_file(path) |
| if payload is None: |
| continue |
| metadata = _saved_map_metadata(payload, path=path) |
| _saved_maps_cache[clean_id] = payload |
| _saved_maps_cache[str(metadata["id"])] = payload |
| _saved_maps_cache[str(metadata["load_id"])] = payload |
| _saved_maps_cache[path.stem] = payload |
| return payload |
|
|
| for path in _iter_saved_map_paths(): |
| payload = _read_saved_map_file(path) |
| if payload is None: |
| continue |
| metadata = _saved_map_metadata(payload, path=path) |
| raw_matches = { |
| path.stem, |
| str(metadata.get("load_id") or ""), |
| str(metadata.get("id") or ""), |
| str(metadata.get("trial_id") or ""), |
| str(metadata.get("name") or ""), |
| } |
| safe_matches = {_safe_stem(match, default="") for match in raw_matches} |
| if clean_id in raw_matches or clean_id in safe_matches: |
| _saved_maps_cache[clean_id] = payload |
| _saved_maps_cache[str(metadata["id"])] = payload |
| _saved_maps_cache[str(metadata["load_id"])] = payload |
| _saved_maps_cache[path.stem] = payload |
| return payload |
| return None |
|
|
|
|
| def _list_saved_maps() -> list[dict[str, Any]]: |
| entries: list[dict[str, Any]] = [] |
| seen_ids: set[str] = set() |
| for path in _iter_saved_map_paths(): |
| payload = _read_saved_map_file(path) |
| if payload is None: |
| continue |
| metadata = _saved_map_metadata(payload, path=path) |
| saved_id = str(metadata.get("load_id") or metadata.get("id") or path.stem) |
| if saved_id in seen_ids: |
| continue |
| seen_ids.add(saved_id) |
| entries.append(metadata) |
| entries.sort(key=lambda item: (int(item.get("saved_at", 0) or 0), str(item.get("id", ""))), reverse=True) |
| return entries |
|
|
|
|
| def _frame_from_env(env: FastFloatingBeaconEnv) -> dict[str, Any]: |
| platforms = env.platforms[0].cpu().tolist() |
| sizes = env.sizes[0].cpu().tolist() |
| cfg = env.cfg |
| return { |
| "platforms": platforms, |
| "sizes": sizes, |
| "goal": int(env.route_len) - 1, |
| "goalStage": int(env.route_len) - 1, |
| "config": plain(cfg), |
| } |
|
|
|
|
| @app.api(name="generate_certified_trial") |
| def generate_certified_trial( |
| difficulty: str = "standard", |
| vibe: str = "cursed vertical wood ruins with fake-looking jumps", |
| seed: int | None = None, |
| generation_mode: str = "procedural", |
| verticality: float = KNOB_DEFAULTS["verticality"], |
| traps: float = KNOB_DEFAULTS["traps"], |
| loopiness: float = KNOB_DEFAULTS["loopiness"], |
| precision: float = KNOB_DEFAULTS["precision"], |
| length: float = KNOB_DEFAULTS["length"], |
| jump_gap: float = KNOB_DEFAULTS["jump_gap"], |
| platform_size: float = KNOB_DEFAULTS["platform_size"], |
| decoys: float = KNOB_DEFAULTS["decoys"], |
| elevation_scale: float = KNOB_DEFAULTS["elevation_scale"], |
| turniness: float = KNOB_DEFAULTS["turniness"], |
| tiny_platforms: float = KNOB_DEFAULTS["tiny_platforms"], |
| large_platforms: float = KNOB_DEFAULTS["large_platforms"], |
| unintuitive: float = KNOB_DEFAULTS["unintuitive"], |
| ) -> dict[str, Any]: |
| with _lock: |
| try: |
| return _certify( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=_generation_mode_alias(generation_mode), |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| ) |
| except Exception as exc: |
| traceback.print_exc() |
| return { |
| "status": "error", |
| "error": str(exc), |
| "error_type": type(exc).__name__, |
| "traceback": traceback.format_exc(), |
| } |
|
|
|
|
| @app.api(name="get_trial") |
| def get_trial(trial_id: str) -> dict[str, Any]: |
| trial = _load_trial(trial_id) |
| if trial is None: |
| return {"status": "missing", "trial_id": trial_id} |
| return trial |
|
|
|
|
| @app.api(name="analyze_attempt") |
| def analyze_attempt(trial_id: str, user_time: float = 0.0, deaths: int = 0) -> dict[str, Any]: |
| trial = _load_trial(trial_id) |
| if trial is None: |
| return {"status": "missing", "trial_id": trial_id} |
| bot_time = float(trial.get("bot", {}).get("par_time", 0.0) or 0.0) |
| delta = float(user_time) - bot_time |
| return { |
| "status": "analyzed", |
| "trial_id": trial_id, |
| "bot_time": bot_time, |
| "user_time": float(user_time), |
| "delta": delta, |
| "deaths": int(deaths), |
| "verdict": "beat_the_bot" if deaths == 0 and delta < 0 else "bot_survived", |
| } |
|
|
|
|
| @app.api(name="runtime_diagnostics") |
| def runtime_diagnostics() -> dict[str, Any]: |
| model: torch.nn.Module | None = None |
| try: |
| _checkpoint, _cfg, model = _load_checkpoint() |
| except Exception: |
| model = None |
| return { |
| "runtime": _runtime_report(model), |
| "physics_defaults": PHYSICS_DEFAULTS, |
| "trial_snapshot_strategy": "batched device rollout, selected rollout JSON materialization", |
| } |
|
|
|
|
| @app.api(name="generate_map") |
| def generate_map(seed: int = 7, route_jumps: int = 12, distractors: int = 6) -> dict[str, Any]: |
| env = _make_env(seed=seed, route_jumps=route_jumps, distractors=distractors) |
| env.generate_maps() |
| return _frame_from_env(env) |
|
|
|
|
| @app.get("/api/generate_certified_trial") |
| def generate_certified_trial_get( |
| difficulty: str = "standard", |
| vibe: str = "cursed vertical wood ruins with fake-looking jumps", |
| seed: int | None = None, |
| generation_mode: str = "procedural", |
| verticality: float = KNOB_DEFAULTS["verticality"], |
| traps: float = KNOB_DEFAULTS["traps"], |
| loopiness: float = KNOB_DEFAULTS["loopiness"], |
| precision: float = KNOB_DEFAULTS["precision"], |
| length: float = KNOB_DEFAULTS["length"], |
| jump_gap: float = KNOB_DEFAULTS["jump_gap"], |
| platform_size: float = KNOB_DEFAULTS["platform_size"], |
| decoys: float = KNOB_DEFAULTS["decoys"], |
| elevation_scale: float = KNOB_DEFAULTS["elevation_scale"], |
| turniness: float = KNOB_DEFAULTS["turniness"], |
| tiny_platforms: float = KNOB_DEFAULTS["tiny_platforms"], |
| large_platforms: float = KNOB_DEFAULTS["large_platforms"], |
| unintuitive: float = KNOB_DEFAULTS["unintuitive"], |
| ) -> JSONResponse: |
| with _lock: |
| try: |
| return JSONResponse(content=_certify( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=_generation_mode_alias(generation_mode), |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| )) |
| except Exception as exc: |
| traceback.print_exc() |
| return JSONResponse( |
| content={ |
| "status": "error", |
| "error": str(exc), |
| "error_type": type(exc).__name__, |
| "traceback": traceback.format_exc(), |
| }, |
| status_code=500, |
| ) |
|
|
|
|
| @app.get("/api/generate_certified_trial_stream") |
| def generate_certified_trial_stream_get( |
| difficulty: str = "standard", |
| vibe: str = "cursed vertical wood ruins with fake-looking jumps", |
| seed: int | None = None, |
| generation_mode: str = "procedural", |
| verticality: float = KNOB_DEFAULTS["verticality"], |
| traps: float = KNOB_DEFAULTS["traps"], |
| loopiness: float = KNOB_DEFAULTS["loopiness"], |
| precision: float = KNOB_DEFAULTS["precision"], |
| length: float = KNOB_DEFAULTS["length"], |
| jump_gap: float = KNOB_DEFAULTS["jump_gap"], |
| platform_size: float = KNOB_DEFAULTS["platform_size"], |
| decoys: float = KNOB_DEFAULTS["decoys"], |
| elevation_scale: float = KNOB_DEFAULTS["elevation_scale"], |
| turniness: float = KNOB_DEFAULTS["turniness"], |
| tiny_platforms: float = KNOB_DEFAULTS["tiny_platforms"], |
| large_platforms: float = KNOB_DEFAULTS["large_platforms"], |
| unintuitive: float = KNOB_DEFAULTS["unintuitive"], |
| ) -> StreamingResponse: |
| """Stream JSONL events. The LLM direct path emits `llm_text_delta` per |
| chunk so the user can watch the platform JSON being written. |
| """ |
| mode = _generation_mode_alias(generation_mode) |
| events: queue.Queue[Any] = queue.Queue() |
| sentinel = object() |
|
|
| def emit(event: dict[str, Any]) -> None: |
| events.put(event) |
|
|
| def worker() -> None: |
| try: |
| with _lock: |
| emit({"type": "status", "message": "starting", "generation_mode": mode}) |
| _certify_stream( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=mode, |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| emit=emit, |
| ) |
| except Exception as exc: |
| traceback.print_exc() |
| emit({ |
| "type": "error", |
| "message": str(exc), |
| "error": str(exc), |
| "error_type": type(exc).__name__, |
| }) |
| finally: |
| events.put(sentinel) |
|
|
| thread = threading.Thread(target=worker, daemon=True) |
| thread.start() |
|
|
| def event_stream(): |
| while True: |
| event = events.get() |
| if event is sentinel: |
| break |
| yield _stream_event(event) |
|
|
| return StreamingResponse( |
| event_stream(), |
| media_type="application/x-ndjson", |
| headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"}, |
| ) |
|
|
|
|
| @app.get("/api/trials/{trial_id}") |
| def get_trial_get(trial_id: str) -> JSONResponse: |
| trial = _load_trial(trial_id) |
| if trial is None: |
| return JSONResponse(content={"status": "missing", "trial_id": trial_id}, status_code=404) |
| return JSONResponse(content=trial) |
|
|
|
|
| @app.get("/api/saved_maps") |
| def list_saved_maps_get() -> JSONResponse: |
| maps = _list_saved_maps() |
| return JSONResponse( |
| content={"status": "ok", "count": len(maps), "maps": maps}, |
| headers={"Cache-Control": "no-store"}, |
| ) |
|
|
|
|
| @app.post("/api/saved_maps") |
| async def save_saved_map_post(request: Request) -> JSONResponse: |
| try: |
| _local_request_only(request) |
| body = await request.json() |
| if not isinstance(body, dict): |
| raise ValueError("expected JSON object") |
| return JSONResponse(content=_save_trial_as_sample( |
| str(body.get("trial_id") or ""), |
| name=body.get("name"), |
| )) |
| except Exception as exc: |
| traceback.print_exc() |
| status = 403 if isinstance(exc, PermissionError) else 500 |
| return JSONResponse( |
| content={"status": "error", "error": str(exc), "error_type": type(exc).__name__}, |
| status_code=status, |
| ) |
|
|
|
|
| @app.get("/api/save_sample_map") |
| def save_saved_map_get(request: Request, trial_id: str, name: str | None = None) -> JSONResponse: |
| try: |
| _local_request_only(request) |
| return JSONResponse(content=_save_trial_as_sample(trial_id, name=name)) |
| except Exception as exc: |
| traceback.print_exc() |
| status = 403 if isinstance(exc, PermissionError) else 500 |
| return JSONResponse( |
| content={"status": "error", "error": str(exc), "error_type": type(exc).__name__}, |
| status_code=status, |
| ) |
|
|
|
|
| @app.get("/api/saved_maps/{saved_id}") |
| def get_saved_map_get(saved_id: str) -> JSONResponse: |
| saved = _load_saved_map(saved_id) |
| if saved is None: |
| return JSONResponse(content={"status": "missing", "saved_id": saved_id}, status_code=404) |
| trial = saved.get("trial") if isinstance(saved.get("trial"), dict) else saved |
| return JSONResponse( |
| content={"status": "ok", "map": _saved_map_metadata(saved), "trial": trial}, |
| headers={"Cache-Control": "no-store"}, |
| ) |
|
|
|
|
| @app.get("/api/runtime_diagnostics") |
| def runtime_diagnostics_get() -> JSONResponse: |
| return JSONResponse(content=runtime_diagnostics()) |
|
|
|
|
| @app.get("/api/generate_map") |
| def generate_map_get(seed: int = 7, route_jumps: int = 12, distractors: int = 6) -> JSONResponse: |
| env = _make_env(seed=seed, route_jumps=route_jumps, distractors=distractors) |
| env.generate_maps() |
| return JSONResponse(content=_frame_from_env(env)) |
|
|
|
|
| def _map_id(vibe: str, difficulty_key: str, seed: int, route_jumps: int, distractors: int) -> str: |
| digest = hashlib.sha1( |
| f"{APP_MAPGEN_VERSION}:{difficulty_key}:{int(seed)}:{int(route_jumps)}:{int(distractors)}:{vibe}".encode("utf-8") |
| ).hexdigest()[:10] |
| return f"map_{difficulty_key}_{int(seed)}_{digest}" |
|
|
|
|
| def _map_path(map_id: str) -> Path: |
| return MAPS_DIR / f"{map_id}.json" |
|
|
|
|
| def _build_map_snapshot_from_env( |
| env: FastFloatingBeaconEnv, |
| *, |
| difficulty_key: str, |
| seed: int, |
| vibe_plan: dict[str, Any], |
| name: str | None = None, |
| ) -> dict[str, Any]: |
| frame = _frame_from_env(env) |
| map_id = _map_id( |
| str(vibe_plan.get("raw_vibe") or ""), |
| difficulty_key, |
| int(seed), |
| int(getattr(env.cfg, "route_jumps", frame.get("goal", 0))), |
| int(getattr(env.cfg, "distractors", 0)), |
| ) |
| return { |
| "id": map_id, |
| "name": _clean_text(name, default=f"{difficulty_key.title()} {seed}", limit=48), |
| "difficulty": str(difficulty_key), |
| "seed": int(seed), |
| "route_jumps": int(getattr(env.cfg, "route_jumps", 0)), |
| "distractors": int(getattr(env.cfg, "distractors", 0)), |
| "style": "floating_platforms", |
| "theme": vibe_plan.get("theme", "misty wood ruins"), |
| "mood": vibe_plan.get("mood", "cool teal fog"), |
| "tags": vibe_plan.get("tags", ["balanced"]), |
| "platforms": frame.get("platforms", []), |
| "sizes": frame.get("sizes", []), |
| "goal": int(frame.get("goal", 0)), |
| "config": frame.get("config", {}), |
| "vibe_plan": {key: value for key, value in vibe_plan.items() if key != "params"}, |
| "params": vibe_plan.get("params", {}), |
| "generated_at": int(time.time()), |
| "mapgen_version": APP_MAPGEN_VERSION, |
| } |
|
|
|
|
| def _save_map_snapshot(snapshot: dict[str, Any]) -> Path: |
| path = _map_path(snapshot["id"]) |
| write_json(path, snapshot, compact=True) |
| _maps_cache[snapshot["id"]] = snapshot |
| return path |
|
|
|
|
| def _load_map_snapshot(map_id: str) -> dict[str, Any] | None: |
| cached = _maps_cache.get(map_id) |
| if cached is not None: |
| return cached |
| path = _map_path(map_id) |
| if not path.exists(): |
| return None |
| try: |
| payload = json.loads(path.read_text(encoding="utf-8")) |
| except (OSError, json.JSONDecodeError): |
| return None |
| _maps_cache[map_id] = payload |
| return payload |
|
|
|
|
| def _list_map_snapshots() -> list[dict[str, Any]]: |
| entries: list[dict[str, Any]] = [] |
| for path in sorted(MAPS_DIR.glob("*.json")): |
| try: |
| payload = json.loads(path.read_text(encoding="utf-8")) |
| except (OSError, json.JSONDecodeError): |
| continue |
| entries.append( |
| { |
| "id": payload.get("id", path.stem), |
| "name": payload.get("name", path.stem), |
| "difficulty": payload.get("difficulty", "standard"), |
| "seed": int(payload.get("seed", 0)), |
| "route_jumps": int(payload.get("route_jumps", 0)), |
| "distractors": int(payload.get("distractors", 0)), |
| "style": payload.get("style", "floating_platforms"), |
| "theme": payload.get("theme", ""), |
| "mood": payload.get("mood", ""), |
| "tags": payload.get("tags", []), |
| "goal": int(payload.get("goal", 0)), |
| "platform_count": len(payload.get("platforms", []) or []), |
| "generated_at": int(payload.get("generated_at", 0)), |
| "mapgen_version": payload.get("mapgen_version", ""), |
| } |
| ) |
| return entries |
|
|
|
|
| def _mapgen_llm_report() -> dict[str, Any]: |
| return { |
| "mode": _llm_mode(), |
| "provider": "openrouter", |
| "model": _llm_model(), |
| "configured": bool(_llm_api_key()), |
| "timeout_seconds": _llm_timeout(), |
| "in_use": _llm_in_use(), |
| } |
|
|
|
|
| @app.api(name="mapgen_config") |
| def mapgen_config() -> dict[str, Any]: |
| return { |
| "mapgen_version": APP_MAPGEN_VERSION, |
| "mapgen_llm": _mapgen_llm_report(), |
| "generation_modes": _generation_mode_labels(), |
| "default_generation_mode": "procedural", |
| "llm_direct_contract": ( |
| "LLM returns short JSON only: {\"platforms\": [{x, y, z, w, d}, ...]}. " |
| "The platforms are ordered from start to goal; no distractors, labels, features, or route flags. " |
| "Backend validates bounds, runs the standard legalizer (separate_route_overlaps, " |
| "enforce_route_reach, etc.) and the trained policy is rolled out against the result." |
| ), |
| } |
|
|
|
|
| @app.api(name="list_cached_maps") |
| def list_cached_maps() -> dict[str, Any]: |
| return { |
| "status": "ok", |
| "count": len(_list_map_snapshots()), |
| "maps": _list_map_snapshots(), |
| } |
|
|
|
|
| @app.api(name="get_cached_map") |
| def get_cached_map(map_id: str) -> dict[str, Any]: |
| snapshot = _load_map_snapshot(map_id) |
| if snapshot is None: |
| return {"status": "missing", "map_id": map_id} |
| return {"status": "ok", "map": snapshot} |
|
|
|
|
| @app.api(name="save_cached_map") |
| def save_cached_map( |
| difficulty: str = "standard", |
| vibe: str = "", |
| seed: int | None = None, |
| generation_mode: str = "procedural", |
| verticality: float = KNOB_DEFAULTS["verticality"], |
| traps: float = KNOB_DEFAULTS["traps"], |
| loopiness: float = KNOB_DEFAULTS["loopiness"], |
| precision: float = KNOB_DEFAULTS["precision"], |
| length: float = KNOB_DEFAULTS["length"], |
| jump_gap: float = KNOB_DEFAULTS["jump_gap"], |
| platform_size: float = KNOB_DEFAULTS["platform_size"], |
| decoys: float = KNOB_DEFAULTS["decoys"], |
| elevation_scale: float = KNOB_DEFAULTS["elevation_scale"], |
| turniness: float = KNOB_DEFAULTS["turniness"], |
| tiny_platforms: float = KNOB_DEFAULTS["tiny_platforms"], |
| large_platforms: float = KNOB_DEFAULTS["large_platforms"], |
| unintuitive: float = KNOB_DEFAULTS["unintuitive"], |
| name: str | None = None, |
| ) -> dict[str, Any]: |
| """Build a map (procedural or LLM direct) and save it to app/maps/. |
| |
| Useful for local map authoring via curl/CLI without running the full |
| policy rollout. The same legalizer is applied to LLM-placed maps. |
| """ |
| if seed is None: |
| seed = int.from_bytes(os.urandom(4), "little") % 1_000_000 |
| requested_knobs = _knobs( |
| verticality, |
| traps, |
| loopiness, |
| precision, |
| length, |
| jump_gap, |
| platform_size, |
| decoys, |
| elevation_scale, |
| turniness, |
| tiny_platforms, |
| large_platforms, |
| unintuitive, |
| ) |
| mode = _generation_mode_alias(generation_mode) |
| if mode == "llm_direct": |
| base_knobs = requested_knobs |
| tuned_spec = _llm_direct_spec(base_knobs) |
| else: |
| spec = _difficulty_spec(difficulty) |
| base_knobs = _difficulty_knobs(str(spec["key"]), requested_knobs) |
| tuned_spec = _spec_with_knobs(spec, base_knobs) |
| route_jumps = int(tuned_spec["route_jumps"]) |
| distractors = int(tuned_spec["distractors"]) |
| if mode == "llm_direct": |
| if not _llm_in_use(): |
| raise RuntimeError("LLM direct path requires MAPGEN_LLM_API_KEY (or OPENROUTER_API_KEY) and MAPGEN_LLM=auto") |
| result = _request_direct_platforms_with_llm(vibe, str(tuned_spec["key"]), route_jumps, distractors) |
| if not result.get("ok"): |
| raise RuntimeError(f"LLM did not produce a usable platform list: {result.get('reason')}") |
| env = _make_env( |
| seed=int(seed), |
| route_jumps=route_jumps, |
| distractors=distractors, |
| direct_platforms=result, |
| ) |
| vibe_plan = { |
| "theme": "llm direct platforms", |
| "mood": "out of distribution", |
| "tags": ["llm-direct", "ood"], |
| "knobs": {}, |
| "params": {}, |
| "raw_vibe": str(vibe or "").strip(), |
| "direct_platforms": { |
| "source": "llm", |
| "model": _llm_model(), |
| "seed": int(seed), |
| "route_count": len(result.get("route", [])), |
| "extras_count": len(result.get("extras", [])), |
| }, |
| } |
| else: |
| vibe_plan = _build_vibe_plan(vibe, str(spec["key"]), base_knobs) |
| env = _make_env( |
| seed=int(seed), |
| route_jumps=route_jumps, |
| distractors=distractors, |
| motif_params=vibe_plan.get("params", {}), |
| ) |
| env.reset() |
| snapshot = _build_map_snapshot_from_env( |
| env, |
| difficulty_key=str(tuned_spec["key"]), |
| seed=int(seed), |
| vibe_plan=vibe_plan, |
| name=name, |
| ) |
| _save_map_snapshot(snapshot) |
| return { |
| "status": "ok", |
| "map_id": snapshot["id"], |
| "platform_count": len(snapshot["platforms"]), |
| "tags": snapshot["tags"], |
| "theme": snapshot["theme"], |
| "mood": snapshot["mood"], |
| "used_llm": bool(vibe_plan.get("direct_platforms", {}).get("source") == "llm"), |
| "generation_mode": mode, |
| "direct_platforms": vibe_plan.get("direct_platforms", {}), |
| } |
|
|
|
|
| @app.get("/api/maps") |
| def list_cached_maps_get() -> JSONResponse: |
| return JSONResponse( |
| content={"status": "ok", "count": len(_list_map_snapshots()), "maps": _list_map_snapshots()}, |
| headers={"Cache-Control": "public, max-age=60"}, |
| ) |
|
|
|
|
| @app.get("/api/maps/{map_id}") |
| def get_cached_map_get(map_id: str): |
| snapshot = _load_map_snapshot(map_id) |
| if snapshot is None: |
| return JSONResponse(content={"status": "missing", "map_id": map_id}, status_code=404) |
| return FileResponse( |
| _map_path(map_id), |
| media_type="application/json", |
| headers={"Cache-Control": "public, max-age=3600, immutable"}, |
| ) |
|
|
|
|
| @app.get("/api/mapgen_config") |
| def mapgen_config_get() -> JSONResponse: |
| return JSONResponse(content=mapgen_config()) |
|
|
|
|
| @app.get("/api/save_cached_map") |
| def save_cached_map_get( |
| difficulty: str = "standard", |
| vibe: str = "", |
| seed: int | None = None, |
| generation_mode: str = "procedural", |
| verticality: float = KNOB_DEFAULTS["verticality"], |
| traps: float = KNOB_DEFAULTS["traps"], |
| loopiness: float = KNOB_DEFAULTS["loopiness"], |
| precision: float = KNOB_DEFAULTS["precision"], |
| length: float = KNOB_DEFAULTS["length"], |
| jump_gap: float = KNOB_DEFAULTS["jump_gap"], |
| platform_size: float = KNOB_DEFAULTS["platform_size"], |
| decoys: float = KNOB_DEFAULTS["decoys"], |
| elevation_scale: float = KNOB_DEFAULTS["elevation_scale"], |
| turniness: float = KNOB_DEFAULTS["turniness"], |
| tiny_platforms: float = KNOB_DEFAULTS["tiny_platforms"], |
| large_platforms: float = KNOB_DEFAULTS["large_platforms"], |
| unintuitive: float = KNOB_DEFAULTS["unintuitive"], |
| name: str | None = None, |
| ) -> JSONResponse: |
| try: |
| return JSONResponse(content=save_cached_map( |
| difficulty=difficulty, |
| vibe=vibe, |
| seed=seed, |
| generation_mode=generation_mode, |
| verticality=verticality, |
| traps=traps, |
| loopiness=loopiness, |
| precision=precision, |
| length=length, |
| jump_gap=jump_gap, |
| platform_size=platform_size, |
| decoys=decoys, |
| elevation_scale=elevation_scale, |
| turniness=turniness, |
| tiny_platforms=tiny_platforms, |
| large_platforms=large_platforms, |
| unintuitive=unintuitive, |
| name=name, |
| )) |
| except Exception as exc: |
| traceback.print_exc() |
| return JSONResponse( |
| content={"status": "error", "error": str(exc), "error_type": type(exc).__name__}, |
| status_code=500, |
| ) |
|
|
|
|
| frontend_dist = Path(__file__).resolve().parent / "frontend" / "dist" |
|
|
|
|
| @app.middleware("http") |
| async def spa_middleware(request: Request, call_next): |
| path = request.url.path |
| if path.startswith("/api"): |
| origin = request.headers.get("origin", "") |
| cors_headers = { |
| "Access-Control-Allow-Origin": origin if origin else "*", |
| "Access-Control-Allow-Methods": "GET,POST,OPTIONS", |
| "Access-Control-Allow-Headers": "content-type,authorization", |
| } |
| if request.method == "OPTIONS": |
| return JSONResponse(content={}, headers=cors_headers) |
| response = await call_next(request) |
| for key, value in cors_headers.items(): |
| response.headers[key] = value |
| return response |
|
|
| if path.startswith("/gradio_api") or not frontend_dist.exists(): |
| return await call_next(request) |
|
|
| target = (frontend_dist / path.lstrip("/")).resolve() |
| if target.is_file() and str(target).startswith(str(frontend_dist.resolve())): |
| return FileResponse(target) |
|
|
| index = frontend_dist / "index.html" |
| if index.exists(): |
| return FileResponse(index) |
|
|
| return await call_next(request) |
|
|
|
|
| if __name__ == "__main__": |
| port = int(os.environ.get("PORT", 7860)) |
| app.launch(server_port=port, show_error=True) |
|
|