/
sususer
/
ColonyGEN
Обзор
Документация
Войти
/
sususer
/
ColonyGEN
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
ml/vae/integration.py
1 785 строк
71 KB
Chekr
f11
03 июн 2026, 13:53
03 июн 2026, 13:53
d260831
Код
Авторство
О чём код?
from __future__ import annotations import copy import time import tracemalloc from pathlib import Path from types import SimpleNamespace from typing import Any import numpy as np from core.difficulty.evaluator import DifficultyEvaluator from core.map.map import GameMap from ml.vae.codec import WorldTensorCodec from ml.vae.dataset import MapDataset, build_condition_vector from ml.vae.model import NumpyVAE def _clamp01(value: float) -> float: return max(0.0, min(1.0, float(value))) def _entropy_strength(target_entropy: float) -> float: return _clamp01((target_entropy - 0.42) / 0.36) def _difficulty_strength(target_difficulty: float | None) -> float: if target_difficulty is None: return 0.5 return _clamp01((float(target_difficulty) - 15.0) / 75.0) def _coherent_noise(height: int, width: int, rng: np.random.Generator) -> np.ndarray: noise = rng.normal(0.0, 1.0, size=(height, width)).astype(np.float32) for _ in range(2): padded = np.pad(noise, 1, mode="reflect") noise = ( padded[1:-1, 1:-1] * 4.0 + padded[:-2, 1:-1] + padded[2:, 1:-1] + padded[1:-1, :-2] + padded[1:-1, 2:] + 0.5 * ( padded[:-2, :-2] + padded[:-2, 2:] + padded[2:, :-2] + padded[2:, 2:] ) ) / 10.0 noise -= float(np.mean(noise)) std = float(np.std(noise)) if std > 1e-6: noise /= std return noise.astype(np.float32) def _rough_noise(height: int, width: int, rng: np.random.Generator) -> np.ndarray: noise = rng.normal(0.0, 1.0, size=(height, width)).astype(np.float32) noise -= float(np.mean(noise)) std = float(np.std(noise)) if std > 1e-6: noise /= std return noise.astype(np.float32) def _chaotic_noise( height: int, width: int, rng: np.random.Generator, entropy_strength: float, ) -> np.ndarray: smooth = _coherent_noise(height, width, rng) rough = _rough_noise(height, width, rng) coarse_h = max(3, height // 6) coarse_w = max(3, width // 6) coarse = rng.normal(0.0, 1.0, size=(coarse_h, coarse_w)).astype(np.float32) coarse = np.repeat(np.repeat(coarse, int(np.ceil(height / coarse_h)), axis=0), int(np.ceil(width / coarse_w)), axis=1) coarse = coarse[:height, :width] coarse -= float(np.mean(coarse)) coarse_std = float(np.std(coarse)) if coarse_std > 1e-6: coarse /= coarse_std mixed = ( smooth * (1.0 - entropy_strength * 0.35) + coarse * (0.30 + entropy_strength * 0.30) + rough * (0.10 + entropy_strength * 0.60) ) mixed -= float(np.mean(mixed)) mixed_std = float(np.std(mixed)) if mixed_std > 1e-6: mixed /= mixed_std return mixed.astype(np.float32) def _normalize_layer(layer: list[list[Any]]) -> list[list[float]]: values = [float(value) for row in layer for value in row] if not values: return [] min_value = min(values) max_value = max(values) if max_value - min_value < 1e-6: return [[0.5 for _ in row] for row in layer] scale = max_value - min_value return [ [round((float(value) - min_value) / scale, 4) for value in row] for row in layer ] def _apply_contrast(layer: list[list[float]], contrast: float) -> list[list[float]]: return [ [round(_clamp01(0.5 + (value - 0.5) * contrast), 4) for value in row] for row in layer ] def _reshape_layers( layers: dict[str, list[list[Any]]], target_entropy: float, target_difficulty: float | None = None, ) -> dict[str, list[list[float]]]: strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) difficulty_bias = difficulty_strength - 0.5 normalized = { name: _normalize_layer(layer) for name, layer in layers.items() } elevation = _apply_contrast(normalized["elevation"], 0.80 + strength * 0.95 + difficulty_strength * 0.25) moisture = _apply_contrast(normalized["moisture"], 0.75 + strength * 1.15 - difficulty_bias * 0.35) fertility = _apply_contrast(normalized["fertility"], 0.70 + strength * 0.85 - difficulty_strength * 0.40) hazard = _apply_contrast(normalized["hazard"], 0.70 + strength * 0.90 + difficulty_strength * 0.55) influence = normalized.get( "influence", [[0.0 for _ in row] for row in elevation], ) return { "elevation": elevation, "moisture": moisture, "fertility": fertility, "hazard": hazard, "influence": influence, } def _postprocess_seed( record: dict[str, Any], target_entropy: float, target_difficulty: float | None, salt: int = 0, ) -> int: metadata = record.get("metadata", {}) target_condition = metadata.get("target_condition", {}) seed = int(metadata.get("seed", 0)) seed += int(metadata.get("sample_index", 0)) * 83492791 seed += int(round(float(target_condition.get("difficulty_score", target_difficulty or 0.0)) * 100.0)) * 29791 seed += int(round(float(target_condition.get("entropy_score", target_entropy)) * 1000.0)) * 12289 layers = record.get("map", {}).get("layers", {}) elevation = layers.get("elevation", []) height = len(elevation) width = len(elevation[0]) if height else 0 for multiplier, (y, x) in zip( (101, 211, 307, 401), ((0, 0), (0, width // 2), (height // 2, width // 2), (height - 1, width - 1)), strict=False, ): if 0 <= y < height and 0 <= x < width: seed += int(round(float(elevation[y][x]) * 10000.0)) * multiplier seed += salt return seed % (2 ** 32 - 1) def _vary_layers( record: dict[str, Any], target_entropy: float, target_difficulty: float | None, ) -> None: layers = record["map"]["layers"] elevation = layers.get("elevation", []) height = len(elevation) width = len(elevation[0]) if height else 0 if height == 0 or width == 0: return rng = np.random.default_rng(_postprocess_seed(record, target_entropy, target_difficulty, salt=157)) entropy_strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) difficulty_bias = difficulty_strength - 0.5 variation_scale = 0.025 + entropy_strength * 0.18 varied: dict[str, list[list[float]]] = {} for layer_name, multiplier in { "elevation": 0.85, "moisture": 1.10, "fertility": 1.00, "hazard": 1.25, }.items(): base = np.asarray(layers[layer_name], dtype=np.float32) noise = _chaotic_noise(height, width, rng, entropy_strength) jitter = _rough_noise(height, width, rng) adjusted = base + noise * variation_scale * multiplier adjusted = adjusted + jitter * entropy_strength * 0.032 * multiplier if layer_name == "elevation": adjusted = adjusted + difficulty_bias * 0.075 + jitter * entropy_strength * 0.025 elif layer_name == "moisture": adjusted = adjusted + (0.46 - difficulty_strength) * 0.065 + noise * entropy_strength * 0.035 elif layer_name == "fertility": adjusted = adjusted + (0.56 - difficulty_strength) * 0.10 + jitter * entropy_strength * 0.03 elif layer_name == "hazard": adjusted = adjusted + difficulty_bias * 0.18 + noise * entropy_strength * 0.05 varied[layer_name] = np.clip(adjusted, 0.0, 1.0).round(4).tolist() varied["influence"] = layers.get("influence", [[0.0 for _ in range(width)] for _ in range(height)]) record["map"]["layers"] = varied def _classify_from_layers( elevation: float, moisture: float, fertility: float, hazard: float, target_entropy: float, target_difficulty: float | None = None, ) -> tuple[str, str]: strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) water_elevation = 0.16 - strength * 0.12 + (0.45 - difficulty_strength) * 0.06 water_moisture = 0.60 - strength * 0.25 - (0.5 - difficulty_strength) * 0.08 mountain_peak = 0.97 - strength * 0.20 - difficulty_strength * 0.22 mountain_high = 0.90 - strength * 0.12 - difficulty_strength * 0.18 hazard_mountain = 0.80 - strength * 0.12 - difficulty_strength * 0.15 sand_moisture = 0.30 + (1.0 - strength) * 0.18 + difficulty_strength * 0.22 dense_moisture = 0.58 - strength * 0.18 + difficulty_strength * 0.15 dense_fertility = 0.50 - strength * 0.18 + difficulty_strength * 0.12 wetland_moisture = 0.76 - strength * 0.20 if elevation < water_elevation and moisture > water_moisture: return "water", "lake" if elevation > mountain_peak: return "mountain", "mountain_range" if hazard > hazard_mountain and elevation > 0.74 - strength * 0.02: return "mountain", "mountain_range" if elevation > mountain_high: return "mountain", "highland" if moisture < sand_moisture and fertility < 0.52 and elevation < 0.60: return "sand", "desert" if moisture > wetland_moisture and elevation < 0.52: return "forest", "wetland" if moisture > dense_moisture and fertility > dense_fertility: if fertility > 0.58 or moisture > 0.66: return "forest", "dense_forest" return "forest", "young_forest" if fertility > 0.56 and moisture > 0.46: return "forest", "young_forest" if elevation > 0.72: return "plain", "highland" return "plain", "grassland" def _rebuild_tiles_from_layers( record: dict[str, Any], target_entropy: float, target_difficulty: float | None = None, ) -> None: layers = record["map"]["layers"] height = len(layers["elevation"]) width = len(layers["elevation"][0]) if height else 0 entropy_strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) rng = np.random.default_rng(_postprocess_seed(record, target_entropy, target_difficulty, salt=229)) terrain_jitter = _chaotic_noise(height, width, rng, entropy_strength) micro_jitter = _rough_noise(height, width, rng) tiles: list[list[dict[str, Any]]] = [] for y in range(height): row = [] for x in range(width): elevation = float(layers["elevation"][y][x]) moisture = float(layers["moisture"][y][x]) fertility = float(layers["fertility"][y][x]) hazard = float(layers["hazard"][y][x]) chaos = float(terrain_jitter[y][x]) micro = float(micro_jitter[y][x]) terrain, biome = _classify_from_layers( _clamp01(elevation + chaos * (0.016 + entropy_strength * 0.026 + difficulty_strength * 0.010) + micro * entropy_strength * 0.010), _clamp01(moisture + chaos * (0.020 + entropy_strength * 0.040) + micro * entropy_strength * 0.018), _clamp01(fertility + chaos * (0.014 + entropy_strength * 0.030) - micro * entropy_strength * 0.014), _clamp01(hazard + chaos * (0.018 + entropy_strength * 0.038 + difficulty_strength * 0.012) + micro * entropy_strength * 0.016), target_entropy, target_difficulty, ) row.append( { "type": terrain, "biome": biome, "resource": 0, "base": False, "locked": terrain == "mountain", "explored": False, } ) tiles.append(row) record["map"]["tiles"] = tiles def _apply_beaches(record: dict[str, Any]) -> None: tiles = record["map"]["tiles"] height = len(tiles) width = len(tiles[0]) if height else 0 for y in range(height): for x in range(width): tile = tiles[y][x] if tile["type"] != "plain": continue neighbors = [] for dy in (-1, 0, 1): for dx in (-1, 0, 1): if dx == 0 and dy == 0: continue ny = y + dy nx = x + dx if 0 <= ny < height and 0 <= nx < width: neighbors.append(tiles[ny][nx]["type"]) if neighbors.count("water") >= 1: tile["type"] = "beach" tile["biome"] = "coast" tile["resource"] = 0 tile["locked"] = False def _neighbor_terrain_counts(tiles: list[list[dict[str, Any]]], x: int, y: int) -> dict[str, int]: height = len(tiles) width = len(tiles[0]) if height else 0 counts: dict[str, int] = {} for dy in (-1, 0, 1): for dx in (-1, 0, 1): if dx == 0 and dy == 0: continue ny = y + dy nx = x + dx if 0 <= ny < height and 0 <= nx < width: terrain = tiles[ny][nx]["type"] counts[terrain] = counts.get(terrain, 0) + 1 return counts def _smooth_structures( record: dict[str, Any], target_entropy: float, target_difficulty: float | None = None, ) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 entropy_strength = _entropy_strength(target_entropy) for _ in range(2 if entropy_strength < 0.58 else 1): updates: list[tuple[int, int, str, str]] = [] for y in range(height): for x in range(width): tile = tiles[y][x] if tile["type"] == "base": continue counts = _neighbor_terrain_counts(tiles, x, y) water_neighbors = counts.get("water", 0) mountain_neighbors = counts.get("mountain", 0) forest_neighbors = counts.get("forest", 0) plain_neighbors = counts.get("plain", 0) elevation = layers["elevation"][y][x] moisture = layers["moisture"][y][x] fertility = layers["fertility"][y][x] hazard = layers["hazard"][y][x] if tile["type"] != "water" and water_neighbors >= 4 + int(entropy_strength >= 0.68) and elevation < 0.32 and moisture > 0.46: updates.append((x, y, "water", "lake")) continue if tile["type"] == "water" and water_neighbors <= 1 and elevation > 0.20: terrain, biome = _classify_from_layers(elevation, moisture, fertility, hazard, target_entropy, target_difficulty) updates.append((x, y, terrain, biome)) continue if tile["type"] != "mountain" and mountain_neighbors >= 5 + int(entropy_strength >= 0.62) and (elevation > 0.78 or hazard > 0.74): biome = "mountain_range" if elevation > 0.9 else "highland" updates.append((x, y, "mountain", biome)) continue if tile["type"] == "mountain" and mountain_neighbors <= 1 and elevation < 0.80 and hazard < 0.74: terrain, biome = _classify_from_layers(elevation, moisture, fertility, hazard, target_entropy, target_difficulty) updates.append((x, y, terrain, biome)) continue if tile["type"] in {"plain", "sand"} and forest_neighbors >= 6 + int(entropy_strength >= 0.55) and moisture > 0.52 and fertility > 0.46: updates.append((x, y, "forest", "young_forest")) continue if tile["type"] == "forest" and plain_neighbors >= 6 + int(entropy_strength >= 0.70) and moisture < 0.50: updates.append((x, y, "plain", "grassland")) if not updates: break for x, y, terrain, biome in updates: tile = tiles[y][x] tile["type"] = terrain tile["biome"] = biome tile["locked"] = terrain == "mountain" tile["resource"] = 0 def _carve_river_from_layers( record: dict[str, Any], target_entropy: float, target_difficulty: float | None, ) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 if height == 0 or width == 0: return entropy_strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) rng = np.random.default_rng(_postprocess_seed(record, target_entropy, target_difficulty, salt=313)) bend_noise = _chaotic_noise(height, width, rng, max(0.18, entropy_strength)) side = ("top", "bottom", "left", "right")[int(rng.integers(0, 4))] def edge_candidates() -> list[tuple[float, int, int, int, int]]: candidates: list[tuple[float, int, int, int, int]] = [] if side in {"top", "bottom"}: y = 0 if side == "top" else height - 1 main_dx, main_dy = 0, 1 if side == "top" else -1 for x in range(2, max(3, width - 2)): elevation = float(layers["elevation"][y][x]) moisture = float(layers["moisture"][y][x]) score = (1.0 - elevation) * 2.6 + moisture * 1.8 + float(rng.random()) * 0.15 candidates.append((score, x, y, main_dx, main_dy)) else: x = 0 if side == "left" else width - 1 main_dx, main_dy = (1, 0) if side == "left" else (-1, 0) for y in range(2, max(3, height - 2)): elevation = float(layers["elevation"][y][x]) moisture = float(layers["moisture"][y][x]) score = (1.0 - elevation) * 2.6 + moisture * 1.8 + float(rng.random()) * 0.15 candidates.append((score, x, y, main_dx, main_dy)) return sorted(candidates, reverse=True)[: max(3, min(8, len(candidates)))] candidates = edge_candidates() if not candidates: return _, x, y, main_dx, main_dy = candidates[int(rng.integers(0, len(candidates)))] visited = set() path: list[tuple[int, int]] = [] max_steps = int((width + height) * (1.35 + entropy_strength * 0.25)) min_steps = max(10, min(width, height) // 2) prev_dx, prev_dy = main_dx, main_dy for step in range(max_steps): if (y, x) in visited: break visited.add((y, x)) path.append((x, y)) tile = tiles[y][x] tile["type"] = "water" tile["biome"] = "river" tile["resource"] = 0 tile["locked"] = False layers["moisture"][y][x] = round(max(float(layers["moisture"][y][x]), 0.78), 4) layers["elevation"][y][x] = round(min(float(layers["elevation"][y][x]), 0.22 + difficulty_strength * 0.04), 4) if step >= min_steps: if side in ("top", "bottom") and y in (0, height - 1): break if side in ("left", "right") and x in (0, width - 1): break valid_candidates: list[tuple[float, int, int]] = [] for ndy in (-1, 0, 1): for ndx in (-1, 0, 1): if ndx == 0 and ndy == 0: continue progress = ndx * main_dx + ndy * main_dy if progress < 0: continue nx = x + ndx ny = y + ndy if not (0 <= nx < width and 0 <= ny < height): continue if (ny, nx) in visited: continue elevation = float(layers["elevation"][ny][nx]) moisture = float(layers["moisture"][ny][nx]) fertility = float(layers["fertility"][ny][nx]) bend = float(bend_noise[ny][nx]) straightness = ndx * prev_dx + ndy * prev_dy score = ( progress * (2.0 - entropy_strength * 0.25) + (1.0 - elevation) * 2.5 + moisture * 1.6 + fertility * 0.35 + bend * 1.8 - max(0, straightness) * 0.40 + float(rng.random()) * (0.45 + entropy_strength * 0.35) ) valid_candidates.append((score, ndx, ndy)) if not valid_candidates: break valid_candidates.sort(reverse=True) _, dx, dy = valid_candidates[int(rng.integers(0, min(3, len(valid_candidates))))] x += dx y += dy prev_dx, prev_dy = dx, dy if len(path) < max(8, min_steps // 2): return widen_chance = 0.05 + entropy_strength * 0.08 + difficulty_strength * 0.03 for x, y in path: if float(rng.random()) > widen_chance: continue direction_idx = int(rng.integers(0, 4)) dx, dy = ((-1, 0), (1, 0), (0, -1), (0, 1))[direction_idx] nx = x + dx ny = y + dy if not (0 <= nx < width and 0 <= ny < height): continue if tiles[ny][nx]["type"] == "base": continue tiles[ny][nx]["type"] = "water" tiles[ny][nx]["biome"] = "river" tiles[ny][nx]["resource"] = 0 tiles[ny][nx]["locked"] = False layers["moisture"][ny][nx] = round(max(float(layers["moisture"][ny][nx]), 0.72), 4) layers["elevation"][ny][nx] = round(min(float(layers["elevation"][ny][nx]), 0.26), 4) def _nearby_count(tiles: list[list[dict[str, Any]]], x: int, y: int, terrain_type: str, radius: int) -> int: height = len(tiles) width = len(tiles[0]) if height else 0 count = 0 for dy in range(-radius, radius + 1): for dx in range(-radius, radius + 1): if dx == 0 and dy == 0: continue ny = y + dy nx = x + dx if 0 <= ny < height and 0 <= nx < width and tiles[ny][nx]["type"] == terrain_type: count += 1 return count def _has_adjacent_resource( tiles: list[list[dict[str, Any]]], x: int, y: int, terrain_filter: str | None = None, ) -> bool: return _adjacent_resource_count(tiles, x, y, terrain_filter=terrain_filter) > 0 def _adjacent_resource_count( tiles: list[list[dict[str, Any]]], x: int, y: int, terrain_filter: str | None = None, ) -> int: height = len(tiles) width = len(tiles[0]) if height else 0 count = 0 for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)): ny = y + dy nx = x + dx if not (0 <= ny < height and 0 <= nx < width): continue tile = tiles[ny][nx] if terrain_filter is not None and tile["type"] != terrain_filter: continue if int(tile.get("resource", 0)) > 0: count += 1 return count def _nearby_resource_potential(layers: dict[str, list[list[float]]], x: int, y: int, radius: int) -> float: height = len(layers["elevation"]) width = len(layers["elevation"][0]) if height else 0 potential = 0.0 for dy in range(-radius, radius + 1): for dx in range(-radius, radius + 1): ny = y + dy nx = x + dx if 0 <= ny < height and 0 <= nx < width: potential += layers["elevation"][ny][nx] * 0.7 potential += layers["fertility"][ny][nx] * 0.5 potential += layers["hazard"][ny][nx] * 0.2 return potential def _place_base(record: dict[str, Any]) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 center_x = width // 2 center_y = height // 2 allowed_radius = max(4, min(width, height) // 3) candidates: list[tuple[float, int, int]] = [] for y in range(height): for x in range(width): tile = tiles[y][x] tile["base"] = False if tile["type"] in {"water", "mountain"}: continue distance = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5 if distance > allowed_radius: continue score = 0.0 if tile["type"] == "plain": score += 8.0 elif tile["type"] == "forest": score += 3.0 elif tile["type"] == "beach": score += 1.0 score += max(0.0, allowed_radius - distance) score += layers["fertility"][y][x] * 4.0 score += (1.0 - layers["hazard"][y][x]) * 5.0 score += _nearby_count(tiles, x, y, "plain", 3) * 0.6 score += _nearby_count(tiles, x, y, "water", 4) * 0.2 score += _nearby_resource_potential(layers, x, y, 6) * 0.15 score -= _nearby_count(tiles, x, y, "mountain", 2) * 1.2 score -= _nearby_count(tiles, x, y, "water", 1) * 2.0 candidates.append((score, x, y)) if not candidates: raise ValueError("No valid base position found for VAE map") candidates.sort(reverse=True) _, base_x, base_y = candidates[0] base_tile = tiles[base_y][base_x] base_tile["type"] = "base" base_tile["biome"] = "colony_core" base_tile["base"] = True base_tile["resource"] = 0 base_tile["locked"] = False record["map"]["base_position"] = {"x": base_x, "y": base_y} def _update_influence_layer(record: dict[str, Any]) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 base = record["map"].get("base_position") if base is None: return bx = int(base["x"]) by = int(base["y"]) radius = max(6, min(width, height) // 4) influence = [] for y in range(height): row = [] for x in range(width): distance = abs(x - bx) + abs(y - by) value = max(0.0, 1.0 - distance / radius) terrain = tiles[y][x]["type"] if terrain == "mountain": value *= 0.35 elif terrain == "water": value *= 0.25 elif terrain == "forest": value *= 0.75 row.append(round(value, 4)) influence.append(row) layers["influence"] = influence def _resource_profile( tile: dict[str, Any], tiles: list[list[dict[str, Any]]], layers: dict[str, list[list[float]]], x: int, y: int, target_difficulty: float | None, ) -> tuple[float, int]: terrain = tile["type"] if terrain not in {"forest", "mountain"}: return 0.0, 0 elevation = layers["elevation"][y][x] fertility = layers["fertility"][y][x] moisture = layers["moisture"][y][x] hazard = layers["hazard"][y][x] influence = layers["influence"][y][x] difficulty_strength = _difficulty_strength(target_difficulty) local_cluster = _nearby_count(tiles, x, y, terrain, 2) local_ring = _nearby_count(tiles, x, y, terrain, 1) remote_bias = 1.0 - influence if terrain == "mountain": weight = 3.4 + elevation * 5.4 + hazard * 1.7 + local_cluster * 0.28 + remote_bias * 0.8 weight += difficulty_strength * 1.2 cap = min(5, 2 + int(difficulty_strength >= 0.45) + int(difficulty_strength >= 0.72) + int(local_ring >= 3)) elif terrain == "forest": interior_bonus = max(0.0, (local_ring - 1) * 0.34) + max(0.0, (local_cluster - 3) * 0.12) weight = 4.6 + fertility * 5.6 + moisture * 3.2 + influence * 0.18 weight += (1.0 - difficulty_strength) * influence * 0.55 + difficulty_strength * remote_bias * 0.22 weight += interior_bonus weight -= max(0, local_cluster - 6) * 0.16 cap = 1 + int(tile.get("biome") == "dense_forest" or local_ring >= 4 or fertility > 0.62) else: return 0.0, 0 return max(0.0, weight), cap def _rebalance_resource_mix( candidates: list[dict[str, Any]], tiles: list[list[dict[str, Any]]], target_total: int, ) -> None: total_assigned = sum(int(candidate["amount"]) for candidate in candidates) if total_assigned <= 0: return forest_candidates = [candidate for candidate in candidates if candidate["terrain"] == "forest"] mountain_candidates = [candidate for candidate in candidates if candidate["terrain"] == "mountain"] forest_capacity = sum(int(candidate["cap"]) for candidate in forest_candidates) mountain_capacity = sum(int(candidate["cap"]) for candidate in mountain_candidates) desired_forest_total = int(round(min(total_assigned, target_total) * 0.60)) desired_forest_total = max(0, min(desired_forest_total, forest_capacity)) min_forest_total = max(0, total_assigned - mountain_capacity) desired_forest_total = max(min_forest_total, desired_forest_total) current_forest_total = sum(int(candidate["amount"]) for candidate in forest_candidates) if current_forest_total < desired_forest_total: deficit = desired_forest_total - current_forest_total growers = sorted( forest_candidates, key=lambda candidate: (candidate["weight"], candidate["cap"] - candidate["amount"]), reverse=True, ) shrinkers = sorted( mountain_candidates, key=lambda candidate: (candidate["weight"], candidate["amount"]), ) for grower in growers: if deficit <= 0: break while grower["amount"] < grower["cap"] and deficit > 0: adjacent = _adjacent_resource_count(tiles, grower["x"], grower["y"], terrain_filter="forest") if adjacent >= 2: break donor = next((candidate for candidate in shrinkers if candidate["amount"] > 0), None) if donor is None: return donor["amount"] -= 1 tiles[donor["y"]][donor["x"]]["resource"] = donor["amount"] grower["amount"] += 1 tiles[grower["y"]][grower["x"]]["resource"] = grower["amount"] deficit -= 1 elif current_forest_total > desired_forest_total: excess = current_forest_total - desired_forest_total shrinkers = sorted( forest_candidates, key=lambda candidate: (candidate["amount"], candidate["weight"]), ) growers = sorted( mountain_candidates, key=lambda candidate: (candidate["weight"], candidate["cap"] - candidate["amount"]), reverse=True, ) for shrinker in shrinkers: if excess <= 0: break while shrinker["amount"] > 0 and excess > 0: receiver = next((candidate for candidate in growers if candidate["amount"] < candidate["cap"]), None) if receiver is None: return shrinker["amount"] -= 1 tiles[shrinker["y"]][shrinker["x"]]["resource"] = shrinker["amount"] receiver["amount"] += 1 tiles[receiver["y"]][receiver["x"]]["resource"] = receiver["amount"] excess -= 1 def _resource_seed( record: dict[str, Any], target_entropy: float, target_difficulty: float | None, salt: int = 0, ) -> int: seed = _postprocess_seed(record, target_entropy, target_difficulty, salt=salt) base = record["map"].get("base_position", {"x": 0, "y": 0}) seed += int(base.get("x", 0)) * 73856093 seed += int(base.get("y", 0)) * 19349663 return seed % (2 ** 32 - 1) def _redistribute_resources(record: dict[str, Any], target_entropy: float, target_difficulty: float | None) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] difficulty_strength = _difficulty_strength(target_difficulty) entropy_strength = _entropy_strength(target_entropy) height = len(tiles) width = len(tiles[0]) if height else 0 for row in tiles: for tile in row: tile["resource"] = 0 tile["locked"] = tile["type"] == "mountain" candidates: list[dict[str, Any]] = [] candidate_lookup: dict[tuple[int, int], dict[str, Any]] = {} for y, row in enumerate(tiles): for x, tile in enumerate(row): weight, cap = _resource_profile(tile, tiles, layers, x, y, target_difficulty) if cap <= 0 or weight <= 0.0: continue candidate = { "x": x, "y": y, "terrain": tile["type"], "cap": cap, "weight": weight, "amount": 0, } candidates.append(candidate) candidate_lookup[(y, x)] = candidate if not candidates: return total_capacity = sum(candidate["cap"] for candidate in candidates) target_total = min(max(320, int(round(height * width * 0.175))), total_capacity) rng = np.random.default_rng(_resource_seed(record, target_entropy, target_difficulty, salt=401)) weights = np.asarray([candidate["weight"] for candidate in candidates], dtype=np.float64) weights_sum = float(np.sum(weights)) if weights_sum <= 0.0: return probabilities = weights / weights_sum cluster_count = max(22, min(56, int(target_total / max(5.8, 12.5 - entropy_strength * 4.0 + difficulty_strength * 1.0)))) remaining = target_total max_weight = max(candidate["weight"] for candidate in candidates) max_depth = 1 + int(entropy_strength >= 0.58) + int(difficulty_strength >= 0.72) for _ in range(cluster_count): if remaining <= 0: break seed_idx = int(rng.choice(len(candidates), p=probabilities)) seed_candidate = candidates[seed_idx] budget = int(min(remaining, rng.integers(3 + int(entropy_strength * 3), 10 + int(difficulty_strength * 4)))) frontier: list[tuple[int, int, float, int]] = [ (seed_candidate["y"], seed_candidate["x"], 1.0, 0) ] visited = {(seed_candidate["y"], seed_candidate["x"])} while frontier and budget > 0 and remaining > 0: y, x, power, depth = frontier.pop(0) candidate = candidate_lookup.get((y, x)) if candidate is None: continue remaining_capacity = candidate["cap"] - candidate["amount"] if remaining_capacity > 0: terrain = tiles[y][x]["type"] max_chunk = 2 if terrain == "mountain" and candidate["amount"] < 3 else 1 suggested = 1 + int(np.ceil(power * max_chunk)) add = min(remaining_capacity, budget, remaining, max_chunk, suggested) if add > 0: candidate["amount"] += add tiles[y][x]["resource"] = candidate["amount"] budget -= add remaining -= add if depth >= max_depth or budget <= 0 or remaining <= 0: continue if tiles[y][x]["type"] == "forest": continue for direction_idx in rng.permutation(4): dy, dx = ((-1, 0), (1, 0), (0, -1), (0, 1))[int(direction_idx)] ny = y + dy nx = x + dx if (ny, nx) in visited or (ny, nx) not in candidate_lookup: continue neighbor = candidate_lookup[(ny, nx)] same_terrain_bonus = 0.15 if tiles[ny][nx]["type"] == tiles[y][x]["type"] else 0.0 layer_similarity = 1.0 - abs(layers["fertility"][ny][nx] - layers["fertility"][y][x]) spread_chance = 0.34 + power * 0.32 + same_terrain_bonus spread_chance += neighbor["weight"] / max_weight * 0.18 + layer_similarity * 0.10 if float(rng.random()) <= min(0.92, spread_chance): frontier.append((ny, nx, power * 0.72, depth + 1)) visited.add((ny, nx)) safety = max(1, target_total * 8) while remaining > 0 and safety > 0: safety -= 1 idx = int(rng.choice(len(candidates), p=probabilities)) candidate = candidates[idx] remaining_capacity = candidate["cap"] - candidate["amount"] if remaining_capacity <= 0: continue terrain = tiles[candidate["y"]][candidate["x"]]["type"] if terrain == "forest" and _adjacent_resource_count(tiles, candidate["x"], candidate["y"], terrain_filter="forest") >= 2: continue max_chunk = 2 if terrain == "mountain" and candidate["amount"] < 2 else 1 add = int(min(remaining, remaining_capacity, rng.integers(1, max_chunk + 1))) if add <= 0: continue candidate["amount"] += add tiles[candidate["y"]][candidate["x"]]["resource"] = candidate["amount"] remaining -= add _rebalance_resource_mix(candidates, tiles, target_total) for candidate in candidates: if candidate["amount"] > 0 and tiles[candidate["y"]][candidate["x"]]["type"] == "forest": if _adjacent_resource_count(tiles, candidate["x"], candidate["y"], terrain_filter="forest") >= 3: candidate["amount"] = max(0, candidate["amount"] - 1) tiles[candidate["y"]][candidate["x"]]["resource"] = candidate["amount"] if tiles[candidate["y"]][candidate["x"]]["type"] == "mountain" and candidate["amount"] > 0: tiles[candidate["y"]][candidate["x"]]["locked"] = True def _smooth_isolated_tiles_simple(record: dict[str, Any], rng: np.random.Generator) -> None: tiles = record["map"]["tiles"] height = len(tiles) width = len(tiles[0]) if height else 0 for y in range(height): for x in range(width): tile = tiles[y][x] terrain = tile["type"] if terrain in ("base",): continue neighbors = _neighbor_terrain_counts(tiles, x, y) neighbor_total = sum(neighbors.values()) if neighbor_total == 0: continue same_terrain = neighbors.get(terrain, 0) isolation = 1.0 - (same_terrain / neighbor_total) if isolation >= 0.75: dominant = max(neighbors.items(), key=lambda kv: kv[1]) if dominant[1] >= 5: new_terrain = dominant[0] tile["type"] = new_terrain if new_terrain == "mountain": tile["locked"] = True tile["biome"] = "mountain_range" elif new_terrain == "water": tile["biome"] = "lake" elif new_terrain == "forest": biomes = ["young_forest", "dense_forest", "wetland"] tile["biome"] = biomes[int(rng.integers(0, len(biomes)))] def _place_base_optimized(record: dict[str, Any], rng: np.random.Generator) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 candidates = [] for y in range(height): for x in range(width): tile = tiles[y][x] if tile["type"] in ("mountain", "water"): continue elevation = float(layers["elevation"][y][x]) fertility = float(layers["fertility"][y][x]) hazard = float(layers["hazard"][y][x]) walkable_neighbors = 0 resource_neighbors = 0 for dy in (-2, -1, 0, 1, 2): for dx in (-2, -1, 0, 1, 2): ny, nx = y + dy, x + dx if 0 <= ny < height and 0 <= nx < width: nt = tiles[ny][nx]["type"] if nt not in ("water", "mountain"): walkable_neighbors += 1 if tiles[ny][nx].get("resource", 0) > 0: resource_neighbors += 1 score = ( elevation * 0.25 + fertility * 0.35 + (1.0 - hazard) * 0.20 + (walkable_neighbors / 24.0) * 0.15 + min(resource_neighbors / 3.0, 1.0) * 0.05 ) candidates.append({"x": x, "y": y, "score": score}) if not candidates: for y in range(height): for x in range(width): if tiles[y][x]["type"] not in ("water", "mountain"): tiles[y][x]["base"] = True tiles[y][x]["type"] = "base" tiles[y][x]["biome"] = "colony_core" record["map"]["base_position"] = {"x": x, "y": y} return return candidates.sort(key=lambda c: -c["score"]) top_candidates = candidates[:max(3, len(candidates) // 10)] chosen = top_candidates[int(rng.integers(0, len(top_candidates)))] cx, cy = chosen["x"], chosen["y"] tiles[cy][cx]["base"] = True tiles[cy][cx]["type"] = "base" tiles[cy][cx]["biome"] = "colony_core" tiles[cy][cx]["resource"] = 0 record["map"]["base_position"] = {"x": cx, "y": cy} def stylize_record( record: dict[str, Any], target_entropy: float, target_difficulty: float | None = None, ) -> dict[str, Any]: stylized = copy.deepcopy(record) stylized["map"]["layers"] = _reshape_layers(stylized["map"]["layers"], target_entropy, target_difficulty) _vary_layers(stylized, target_entropy, target_difficulty) _rebuild_tiles_from_layers(stylized, target_entropy, target_difficulty) _smooth_structures(stylized, target_entropy, target_difficulty) _carve_river_from_layers(stylized, target_entropy, target_difficulty) _apply_beaches(stylized) _place_base(stylized) _update_influence_layer(stylized) _redistribute_resources(stylized, target_entropy, target_difficulty) stylized.setdefault("metadata", {})["vae_postprocess"] = { "target_entropy": round(target_entropy, 4), "target_difficulty": None if target_difficulty is None else round(float(target_difficulty), 2), "entropy_strength": round(_entropy_strength(target_entropy), 4), "difficulty_strength": round(_difficulty_strength(target_difficulty), 4), } return stylized def stylize_record_simple( record: dict[str, Any], target_entropy: float, target_difficulty: float | None = None, ) -> dict[str, Any]: stylized = copy.deepcopy(record) entropy_strength = _entropy_strength(target_entropy) rng = np.random.default_rng(_postprocess_seed(record, target_entropy, target_difficulty, salt=999)) # Key step: reshape layers for contrast based on entropy/difficulty stylized["map"]["layers"] = _reshape_layers( stylized["map"]["layers"], target_entropy, target_difficulty ) # Key step: rebuild tiles from reshaped layers - this creates terrain variety! _rebuild_tiles_from_layers(stylized, target_entropy, target_difficulty) # Apply biomes after rebuild tiles = stylized["map"]["tiles"] height = len(tiles) width = len(tiles[0]) if height else 0 for y in range(height): for x in range(width): tile = tiles[y][x] terrain = tile["type"] if terrain == "water": tile["biome"] = "lake" elif terrain == "beach": tile["biome"] = "coast" elif terrain == "mountain": tile["biome"] = "mountain_range" tile["locked"] = True elif terrain == "sand": tile["biome"] = "desert" elif terrain == "forest": biomes = ["young_forest", "dense_forest", "wetland"] tile["biome"] = biomes[int(rng.integers(0, len(biomes)))] elif terrain == "plain": biomes = ["grassland", "highland"] tile["biome"] = biomes[int(rng.integers(0, len(biomes)))] _smooth_isolated_tiles_simple(stylized, rng) _apply_beaches(stylized) _place_base_optimized(stylized, rng) _update_influence_layer(stylized) _redistribute_resources_simple(stylized, target_entropy, target_difficulty, rng) stylized.setdefault("metadata", {})["vae_postprocess"] = { "mode": "simple", "target_entropy": round(target_entropy, 4), "target_difficulty": None if target_difficulty is None else round(float(target_difficulty), 2), "entropy_strength": round(entropy_strength, 4), } return stylized def _redistribute_resources_simple( record: dict[str, Any], target_entropy: float, target_difficulty: float | None, rng: np.random.Generator, ) -> None: tiles = record["map"]["tiles"] layers = record["map"]["layers"] height = len(tiles) width = len(tiles[0]) if height else 0 difficulty_strength = _difficulty_strength(target_difficulty) # Minimum 300 resources total (was 60-100) base_target = max(300, 180 + int(difficulty_strength * 120)) forest_candidates = [] mountain_candidates = [] for y in range(height): for x in range(width): tile = tiles[y][x] if tile.get("base", False): tile["resource"] = 0 continue tile["resource"] = 0 elevation = float(layers["elevation"][y][x]) fertility = float(layers["fertility"][y][x]) hazard = float(layers["hazard"][y][x]) if tile["type"] == "forest": score = (fertility * 0.6 + (1.0 - hazard) * 0.4) * (1.0 + rng.random() * 0.3) forest_candidates.append({"x": x, "y": y, "score": score}) elif tile["type"] == "mountain" and tile.get("locked", False): score = elevation * (1.0 + rng.random() * 0.2) mountain_candidates.append({"x": x, "y": y, "score": score}) forest_candidates.sort(key=lambda c: -c["score"]) mountain_candidates.sort(key=lambda c: -c["score"]) # 60% in forests, 35% in mountains, 5% mixed forest_total = max(180, int(base_target * 0.60)) forest_cap = 2 remaining_forest = forest_total for candidate in forest_candidates: if remaining_forest <= 0: break amount = min(forest_cap, remaining_forest) tiles[candidate["y"]][candidate["x"]]["resource"] = amount remaining_forest -= amount mountain_total = max(105, int(base_target * 0.35)) mountain_cap = 5 remaining_mountain = mountain_total for candidate in mountain_candidates: if remaining_mountain <= 0: break amount = min(mountain_cap, remaining_mountain) tiles[candidate["y"]][candidate["x"]]["resource"] = amount remaining_mountain -= amount def _terrain_ratios(record: dict[str, Any]) -> dict[str, float]: counts = { "water": 0, "beach": 0, "plain": 0, "forest": 0, "mountain": 0, "sand": 0, "base": 0, } total = 0 for row in record["map"]["tiles"]: for tile in row: total += 1 terrain = tile.get("type", "plain") if terrain in counts: counts[terrain] += 1 return { key: (value / max(1, total)) for key, value in counts.items() } def load_model_bundle(model_path: str | Path, dataset_root: str | Path) -> tuple[NumpyVAE, WorldTensorCodec, dict[str, Any]]: model, codec_config, metadata = NumpyVAE.load(model_path) dataset = MapDataset(dataset_root, "train") codec = dataset.codec if not codec_config else WorldTensorCodec.from_manifest(codec_config) if model.config.map_width != 48 or model.config.map_height != 48: raise ValueError( "Legacy VAE model size detected. " f"Expected 48x48, got {model.config.map_width}x{model.config.map_height}. " "Regenerate the 48x48 dataset and retrain the model." ) if codec.config.map_width != 48 or codec.config.map_height != 48: raise ValueError( "Legacy dataset manifest size detected for VAE generation. " f"Expected 48x48, got {codec.config.map_width}x{codec.config.map_height}. " "Regenerate the dataset before using VAE generation." ) return model, codec, metadata def _attach_generation_stats( record: dict[str, Any], started: float, seed: int, target_difficulty: float, target_entropy: float, summary: dict[str, Any], ) -> None: height = len(record.get("map", {}).get("tiles", [])) width = len(record.get("map", {}).get("tiles", [[]])[0]) if height else 0 _, peak_memory = tracemalloc.get_traced_memory() record.setdefault("metadata", {})["generation_stats"] = { "mode": str(summary.get("generation_mode", "vae_generation")), "elapsed_s": round(time.perf_counter() - started, 4), "peak_memory_mb": round(peak_memory / (1024 * 1024), 3), "width": width, "height": height, "seed": int(seed), "target_difficulty": round(float(target_difficulty), 2), "target_entropy": round(float(target_entropy), 4), "evaluated_candidates": int(summary.get("evaluated_candidates", 0)), "reroll_attempts": int(summary.get("reroll_attempts", 0)), } def sample_records( model_path: str | Path, dataset_root: str | Path, count: int, temperature: float, seed: int, target_difficulty: float | None = None, target_entropy: float | None = None, simple_mode: bool = False, ) -> list[dict[str, Any]]: model, codec, _ = load_model_bundle(model_path, dataset_root) condition = None if model.config.condition_dim > 0 and target_difficulty is not None and target_entropy is not None: condition = build_condition_vector(target_difficulty, target_entropy) tensors = model.sample(count=count, temperature=temperature, seed=seed, condition=condition) records = [] stylizer = stylize_record_simple if simple_mode else stylize_record for idx, tensor in enumerate(tensors): record = codec.decode_tensor( tensor, metadata={ "generator": "numpy_vae_ui", "temperature": temperature, "sample_index": idx, "seed": int(seed + idx * 104729), **( { "target_condition": { "difficulty_score": float(target_difficulty), "entropy_score": float(target_entropy), } } if condition is not None else {} ), }, split="generated", ) if target_entropy is not None: record = stylizer(record, target_entropy, target_difficulty) records.append(record) return records def evaluate_record(record: dict[str, Any]) -> dict[str, Any]: game_map = GameMap.from_dataset_record(record) proxy_state = SimpleNamespace(map=game_map) result = DifficultyEvaluator(proxy_state).evaluate() vector = result["vector"] terrain_ratios = _terrain_ratios(record) entropy_score = (vector["terrain_entropy"] + vector["biome_entropy"]) / 2.0 layer_complexity = ( vector["elevation_std"] + vector["moisture_std"] + vector["fertility_std"] + vector["hazard_std"] ) / 4.0 terrain_presence = sum( 1 for key, threshold in { "water": 0.015, "beach": 0.01, "plain": 0.12, "forest": 0.12, "mountain": 0.015, "sand": 0.01, }.items() if terrain_ratios[key] >= threshold ) return { **result, "entropy_score": entropy_score, "layer_complexity": layer_complexity, "terrain_presence": terrain_presence, "terrain_ratios": terrain_ratios, } def _playability_breakdown(metrics: dict[str, Any]) -> dict[str, Any]: vector = metrics["vector"] resource_density = float(vector.get("resource_density", 0.0)) resource_amount_ratio = float(vector.get("resource_amount_ratio", 0.0)) reachable_resource_ratio = float(vector.get("reachable_resource_ratio", 0.0)) reachable_resource_amount_ratio = float(vector.get("reachable_resource_amount_ratio", reachable_resource_ratio)) reachable_area_ratio = float(vector.get("reachable_area_ratio", 0.0)) largest_component_ratio = float(vector.get("largest_walkable_component_ratio", 0.0)) bridgeable_water_ratio = float(vector.get("bridgeable_water_ratio", 1.0)) water_ratio = float(vector.get("water_ratio", 0.0)) walkable_component_count = int(vector.get("walkable_component_count", 0)) penalty = 0.0 penalty += max(0.0, 0.012 - resource_density) * 2600.0 penalty += max(0.0, 0.70 - resource_amount_ratio) * 90.0 penalty += max(0.0, 0.120 - reachable_resource_ratio) * 560.0 penalty += max(0.0, 0.180 - reachable_resource_amount_ratio) * 620.0 penalty += max(0.0, 0.100 - reachable_area_ratio) * 420.0 penalty += max(0.0, 0.580 - largest_component_ratio) * 360.0 if water_ratio > 0.08: penalty += max(0.0, 0.32 - bridgeable_water_ratio) * 180.0 if walkable_component_count > 14 and largest_component_ratio < 0.72: penalty += (walkable_component_count - 14) * 1.6 is_playable = ( resource_density >= 0.012 and resource_amount_ratio >= 0.70 and reachable_resource_ratio >= 0.120 and reachable_resource_amount_ratio >= 0.180 and reachable_area_ratio >= 0.100 and largest_component_ratio >= 0.580 and (water_ratio < 0.08 or bridgeable_water_ratio >= 0.32) ) return { "is_playable": bool(is_playable), "playability_penalty": penalty, "resource_density": resource_density, "resource_amount_ratio": resource_amount_ratio, "reachable_resource_ratio": reachable_resource_ratio, "reachable_resource_amount_ratio": reachable_resource_amount_ratio, "reachable_area_ratio": reachable_area_ratio, "largest_walkable_component_ratio": largest_component_ratio, "bridgeable_water_ratio": bridgeable_water_ratio, "walkable_component_count": walkable_component_count, } def _selection_breakdown(metrics: dict[str, Any], target_difficulty: float, target_entropy: float) -> dict[str, float]: strength = _entropy_strength(target_entropy) difficulty_strength = _difficulty_strength(target_difficulty) vector = metrics["vector"] ratios = metrics["terrain_ratios"] playability = _playability_breakdown(metrics) target_layer_complexity = 0.11 + strength * 0.12 + abs(difficulty_strength - 0.5) * 0.025 target_water_ratio = 0.025 + strength * 0.05 + difficulty_strength * 0.01 target_mountain_ratio = 0.035 + strength * 0.05 + difficulty_strength * 0.055 target_sand_ratio = 0.008 + difficulty_strength * 0.03 target_resource_ratio = 1.08 - difficulty_strength * 0.38 target_reachable_amount_ratio = 0.62 - difficulty_strength * 0.18 difficulty_distance = abs(metrics["difficulty_score"] - target_difficulty) * (1.35 + difficulty_strength * 0.65) entropy_distance = abs(metrics["entropy_score"] - target_entropy) * (85.0 + strength * 95.0) structure_distance = abs(metrics["layer_complexity"] - target_layer_complexity) * 220.0 terrain_distance = abs(ratios["water"] - target_water_ratio) * 120.0 terrain_distance += abs(ratios["mountain"] - target_mountain_ratio) * 120.0 terrain_distance += abs(ratios["sand"] - target_sand_ratio) * 55.0 economy_distance = abs(float(vector.get("resource_amount_ratio", 0.0)) - target_resource_ratio) * 28.0 economy_distance += abs(float(vector.get("reachable_resource_amount_ratio", 0.0)) - target_reachable_amount_ratio) * 60.0 flat_penalty = max(0.0, 0.095 - metrics["layer_complexity"]) * 260.0 presence_penalty = max(0, 4 - metrics["terrain_presence"]) * 7.5 coast_penalty = 8.0 if ratios["water"] > 0.02 and ratios["beach"] < 0.005 else 0.0 selection_distance = ( difficulty_distance + entropy_distance + structure_distance + terrain_distance + economy_distance + flat_penalty + presence_penalty + coast_penalty + playability["playability_penalty"] ) return { "selection_distance": selection_distance, "target_difficulty": target_difficulty, "target_entropy": target_entropy, "target_layer_complexity": target_layer_complexity, "target_water_ratio": target_water_ratio, "target_mountain_ratio": target_mountain_ratio, "target_sand_ratio": target_sand_ratio, "target_resource_ratio": target_resource_ratio, "difficulty_distance": difficulty_distance, "entropy_distance": entropy_distance, "structure_distance": structure_distance, "terrain_distance": terrain_distance, "economy_distance": economy_distance, "flat_penalty": flat_penalty, "presence_penalty": presence_penalty, "coast_penalty": coast_penalty, "playability_penalty": playability["playability_penalty"], "is_playable": float(playability["is_playable"]), "reachable_area_ratio": playability["reachable_area_ratio"], "reachable_resource_ratio": playability["reachable_resource_ratio"], "reachable_resource_amount_ratio": playability["reachable_resource_amount_ratio"], "largest_walkable_component_ratio": playability["largest_walkable_component_ratio"], } def _decode_guided_record( model: NumpyVAE, codec: WorldTensorCodec, latent: np.ndarray, temperature: float, sample_index: int, target_difficulty: float, target_entropy: float, simple_mode: bool = False, ) -> dict[str, Any]: tensor = model.decode(np.asarray(latent, dtype=np.float32).reshape(1, -1))[0] record = codec.decode_tensor( tensor, metadata={ "generator": "numpy_vae_guided", "temperature": temperature, "sample_index": sample_index, "seed": int(sample_index), "target_condition": { "difficulty_score": float(target_difficulty), "entropy_score": float(target_entropy), }, }, split="generated", ) stylizer = stylize_record_simple if simple_mode else stylize_record return stylizer(record, target_entropy, target_difficulty) def _decode_conditioned_record( model: NumpyVAE, codec: WorldTensorCodec, condition: np.ndarray, temperature: float, seed: int, sample_index: int, target_difficulty: float, target_entropy: float, simple_mode: bool = False, ) -> dict[str, Any]: tensor = model.sample( count=1, temperature=temperature, seed=seed, condition=condition, )[0] record = codec.decode_tensor( tensor, metadata={ "generator": "numpy_cvae_ui", "temperature": temperature, "sample_index": sample_index, "seed": int(seed), "target_condition": { "difficulty_score": float(target_difficulty), "entropy_score": float(target_entropy), }, }, split="generated", ) stylizer = stylize_record_simple if simple_mode else stylize_record return stylizer(record, target_entropy, target_difficulty) def _select_preferred_candidate( current: tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | None, record: dict[str, Any], metrics: dict[str, Any], selection: dict[str, Any], ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: if current is None: return record, metrics, selection _, _, current_selection = current if float(selection["selection_distance"]) < float(current_selection["selection_distance"]): return record, metrics, selection return current def _generate_conditioned_record_by_targets_once( model: NumpyVAE, codec: WorldTensorCodec, target_difficulty: float, target_entropy: float, temperature: float, seed: int, simple_mode: bool = False, ) -> tuple[ tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | None, tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | None, int, ]: condition = build_condition_vector(target_difficulty, target_entropy) candidate_count = 5 + 5 * int(_entropy_strength(target_entropy) >= 0.72) best_playable = None best_any = None for candidate_idx in range(candidate_count): candidate_seed = int(seed + candidate_idx * 15485863) record = _decode_conditioned_record( model, codec, condition, temperature, candidate_seed, candidate_idx, target_difficulty, target_entropy, simple_mode, ) metrics = evaluate_record(record) selection = _selection_breakdown(metrics, target_difficulty, target_entropy) best_any = _select_preferred_candidate(best_any, record, metrics, selection) if bool(selection["is_playable"]): best_playable = _select_preferred_candidate(best_playable, record, metrics, selection) return best_playable, best_any, candidate_count def _generate_guided_record_by_targets_once( model: NumpyVAE, codec: WorldTensorCodec, target_difficulty: float, target_entropy: float, temperature: float, seed: int, simple_mode: bool = False, ) -> tuple[ tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | None, tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | None, dict[str, int], ]: rng = np.random.default_rng(seed) latent_dim = model.config.latent_dim base_scale = max(0.45, temperature) restarts = 3 steps = 5 branch_factor = 4 best_playable = None best_any = None evaluated_candidates = 0 accepted_steps = 0 for restart in range(restarts): current_latent = rng.normal(0.0, base_scale, size=(latent_dim,)).astype(np.float32) current_record = _decode_guided_record( model, codec, current_latent, temperature, sample_index=restart * 1000, target_difficulty=target_difficulty, target_entropy=target_entropy, simple_mode=simple_mode, ) current_metrics = evaluate_record(current_record) current_selection = _selection_breakdown(current_metrics, target_difficulty, target_entropy) current_distance = current_selection["selection_distance"] evaluated_candidates += 1 best_any = _select_preferred_candidate(best_any, current_record, current_metrics, current_selection) if bool(current_selection["is_playable"]): best_playable = _select_preferred_candidate(best_playable, current_record, current_metrics, current_selection) sigma = base_scale * 0.58 for step in range(steps): local_best_distance = current_distance local_best_latent = current_latent local_best_record = current_record local_best_metrics = current_metrics local_best_selection = current_selection for branch in range(branch_factor): proposal_latent = current_latent + rng.normal(0.0, sigma, size=(latent_dim,)).astype(np.float32) proposal_record = _decode_guided_record( model, codec, proposal_latent, temperature, sample_index=restart * 1000 + step * 100 + branch + 1, target_difficulty=target_difficulty, target_entropy=target_entropy, simple_mode=simple_mode, ) proposal_metrics = evaluate_record(proposal_record) proposal_selection = _selection_breakdown(proposal_metrics, target_difficulty, target_entropy) proposal_distance = proposal_selection["selection_distance"] evaluated_candidates += 1 best_any = _select_preferred_candidate(best_any, proposal_record, proposal_metrics, proposal_selection) if bool(proposal_selection["is_playable"]): best_playable = _select_preferred_candidate(best_playable, proposal_record, proposal_metrics, proposal_selection) if proposal_distance < local_best_distance: local_best_distance = proposal_distance local_best_latent = proposal_latent local_best_record = proposal_record local_best_metrics = proposal_metrics local_best_selection = proposal_selection if local_best_distance + 1e-6 < current_distance: current_latent = local_best_latent current_record = local_best_record current_metrics = local_best_metrics current_selection = local_best_selection current_distance = local_best_distance accepted_steps += 1 else: current_latent = current_latent + rng.normal(0.0, sigma * 0.35, size=(latent_dim,)).astype(np.float32) sigma *= 0.72 return best_playable, best_any, { "latent_restarts": restarts, "latent_steps": steps, "latent_branch_factor": branch_factor, "evaluated_candidates": evaluated_candidates, "accepted_steps": accepted_steps, } def generate_record_by_targets( model_path: str | Path, dataset_root: str | Path, target_difficulty: float, target_entropy: float, temperature: float, seed: int, simple_mode: bool = False, ) -> tuple[dict[str, Any], dict[str, Any]]: model, codec, _ = load_model_bundle(model_path, dataset_root) trace_started_here = False if not tracemalloc.is_tracing(): tracemalloc.start() trace_started_here = True started = time.perf_counter() max_rerolls = 4 # 5 rounds max × 10 candidates = 50 max total try: if model.config.condition_dim > 0: best_playable = None best_any = None total_candidates = 0 reroll_attempts = 0 for reroll_attempt in range(max_rerolls + 1): reroll_seed = int(seed + reroll_attempt * 32452843) playable_round, any_round, candidate_count = _generate_conditioned_record_by_targets_once( model, codec, target_difficulty, target_entropy, temperature, reroll_seed, simple_mode, ) total_candidates += candidate_count if any_round is not None: best_any = _select_preferred_candidate(best_any, any_round[0], any_round[1], any_round[2]) if playable_round is not None: # Track best playable across all rerolls best_playable = _select_preferred_candidate(best_playable, playable_round[0], playable_round[1], playable_round[2]) reroll_attempts = reroll_attempt # Only stop early if we have a good playable match if best_playable is not None and playable_round is not None: if playable_round[2].get("selection_distance", 999) < 150: break # Strict: use playable only, fail if none found after max_rerolls chosen = best_playable if best_playable is not None else best_any if chosen is None: raise ValueError("Failed to generate any VAE record") if best_playable is None: # Log warning but still return best_any with flag import warnings warnings.warn(f"No playable map found after {max_rerolls} rerolls, using fallback") best_record, best_metrics, best_selection = chosen generation_info = { "generation_mode": "conditional_multi_sample", "evaluated_candidates": total_candidates, "accepted_steps": 0, "reroll_attempts": reroll_attempts, "playability_fallback_used": 0 if best_playable is not None else 1, } best_record.setdefault("metadata", {})["selection"] = { **best_metrics, **best_selection, **generation_info, } best_record["metadata"]["guided_generation"] = generation_info summary = { **best_metrics, **best_selection, **generation_info, } _attach_generation_stats(best_record, started, seed, target_difficulty, target_entropy, summary) return best_record, summary best_playable = None best_any = None guided_info = None reroll_attempts = 0 for reroll_attempt in range(max_rerolls + 1): reroll_seed = int(seed + reroll_attempt * 32452843) playable_round, any_round, round_info = _generate_guided_record_by_targets_once( model, codec, target_difficulty, target_entropy, temperature, reroll_seed, simple_mode, ) guided_info = round_info if guided_info is None else { "latent_restarts": round_info["latent_restarts"], "latent_steps": round_info["latent_steps"], "latent_branch_factor": round_info["latent_branch_factor"], "evaluated_candidates": guided_info["evaluated_candidates"] + round_info["evaluated_candidates"], "accepted_steps": guided_info["accepted_steps"] + round_info["accepted_steps"], } if any_round is not None: best_any = _select_preferred_candidate(best_any, any_round[0], any_round[1], any_round[2]) if playable_round is not None: best_playable = _select_preferred_candidate(best_playable, playable_round[0], playable_round[1], playable_round[2]) reroll_attempts = reroll_attempt break reroll_attempts = reroll_attempt chosen = best_playable or best_any if chosen is None or guided_info is None: raise ValueError("Failed to generate VAE record by targets") best_record, best_metrics, best_selection = chosen generation_info = { "generation_mode": "guided_latent_search", "latent_restarts": guided_info["latent_restarts"], "latent_steps": guided_info["latent_steps"], "latent_branch_factor": guided_info["latent_branch_factor"], "evaluated_candidates": guided_info["evaluated_candidates"], "accepted_steps": guided_info["accepted_steps"], "reroll_attempts": reroll_attempts, "playability_fallback_used": 0 if best_playable is not None else 1, } best_record.setdefault("metadata", {})["selection"] = { **best_metrics, **best_selection, **generation_info, } best_record["metadata"]["guided_generation"] = generation_info summary = { **best_metrics, **best_selection, **generation_info, } _attach_generation_stats(best_record, started, seed, target_difficulty, target_entropy, summary) return best_record, summary finally: if trace_started_here: tracemalloc.stop() def select_record_by_targets( records: list[dict[str, Any]], target_difficulty: float, target_entropy: float, ) -> tuple[dict[str, Any], dict[str, Any]]: best_record = None best_metrics = None best_distance = None for record in records: metrics = evaluate_record(record) selection = _selection_breakdown(metrics, target_difficulty, target_entropy) distance = selection["selection_distance"] if best_distance is None or distance < best_distance: best_distance = distance best_record = record best_metrics = { **metrics, **selection, } if best_record is None or best_metrics is None: raise ValueError("No VAE records available for selection") best_record.setdefault("metadata", {})["selection"] = best_metrics return best_record, best_metrics