244 lines
7.0 KiB
Python
244 lines
7.0 KiB
Python
|
|
"""Superpower Assignment Algorithm.
|
|||
|
|
|
|||
|
|
Assigns superpowers to glyphs based on:
|
|||
|
|
1. Specialized type (if any)
|
|||
|
|
2. Dynamic metrics calculation (5-25 range for G002-G600)
|
|||
|
|
3. Band eligibility
|
|||
|
|
4. Scoring and ranking
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import hashlib
|
|||
|
|
from typing import Dict, List, Any, Optional
|
|||
|
|
from .superpower_registry import (
|
|||
|
|
load_all_superpowers,
|
|||
|
|
get_superpower,
|
|||
|
|
get_superpowers_by_band,
|
|||
|
|
get_superpowers_by_bands,
|
|||
|
|
)
|
|||
|
|
from .specialized_types import get_specialized_type, get_type_config, SPECIALIZED_GLYPH_TYPES
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_power_count(metrics: Dict[str, Any], specialized_type: str = "") -> int:
|
|||
|
|
"""Calculate number of superpowers for a glyph (5-25 range).
|
|||
|
|
|
|||
|
|
Formula: power_count = 5 + int((avg_metric / 100) * 20)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
metrics: Glyph metrics dict (power, complexity, resonance, stability, connectivity, affinity)
|
|||
|
|
specialized_type: Optional specialized type name
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Power count (5-25)
|
|||
|
|
"""
|
|||
|
|
# Get metric values
|
|||
|
|
values = [
|
|||
|
|
metrics.get("power", 50),
|
|||
|
|
metrics.get("complexity", 50),
|
|||
|
|
metrics.get("resonance", 50),
|
|||
|
|
metrics.get("stability", 50),
|
|||
|
|
metrics.get("connectivity", 50),
|
|||
|
|
metrics.get("affinity", 50),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
avg_metric = sum(values) / len(values)
|
|||
|
|
|
|||
|
|
# Calculate base count (5-25 range)
|
|||
|
|
base_count = 5 + int((avg_metric / 100) * 20)
|
|||
|
|
|
|||
|
|
# Clamp to range
|
|||
|
|
base_count = max(5, min(25, base_count))
|
|||
|
|
|
|||
|
|
# Override for specialized types
|
|||
|
|
if specialized_type:
|
|||
|
|
type_config = get_type_config(specialized_type)
|
|||
|
|
min_powers = type_config.get("min_powers", 5)
|
|||
|
|
max_powers = type_config.get("max_powers", 25)
|
|||
|
|
|
|||
|
|
# For aether_node, always 152
|
|||
|
|
if specialized_type == "aether_node":
|
|||
|
|
return 152
|
|||
|
|
|
|||
|
|
# Clamp to type's range
|
|||
|
|
base_count = max(min_powers, min(max_powers, base_count))
|
|||
|
|
|
|||
|
|
return base_count
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_eligible_bands(glyph_id: str, specialized_type: str = "") -> List[str]:
|
|||
|
|
"""Get eligible superpower bands for a glyph.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
glyph_id: Glyph ID
|
|||
|
|
specialized_type: Optional specialized type
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
List of band identifiers (e.g., ["A", "B"])
|
|||
|
|
"""
|
|||
|
|
# Aether node gets all bands
|
|||
|
|
if specialized_type == "aether_node":
|
|||
|
|
return ["A", "B", "C", "D"]
|
|||
|
|
|
|||
|
|
# Monument grade gets all bands
|
|||
|
|
if specialized_type == "monument_grade_equilibrium":
|
|||
|
|
return ["A", "B", "C", "D"]
|
|||
|
|
|
|||
|
|
# Get type config
|
|||
|
|
if specialized_type:
|
|||
|
|
type_config = get_type_config(specialized_type)
|
|||
|
|
preferred_ids = type_config.get("preferred_superpower_ids", [])
|
|||
|
|
|
|||
|
|
# Determine bands from preferred IDs
|
|||
|
|
bands = set()
|
|||
|
|
for id in preferred_ids:
|
|||
|
|
if id <= 15:
|
|||
|
|
bands.add("A")
|
|||
|
|
elif id <= 45:
|
|||
|
|
bands.add("B")
|
|||
|
|
elif id <= 76:
|
|||
|
|
bands.add("C")
|
|||
|
|
else:
|
|||
|
|
bands.add("D")
|
|||
|
|
|
|||
|
|
return list(bands)
|
|||
|
|
|
|||
|
|
# Default: based on glyph ID (family/tier)
|
|||
|
|
glyph_num = int(glyph_id[1:]) if glyph_id.startswith("G") else 1
|
|||
|
|
tier = ((glyph_num - 1) // 10) + 1
|
|||
|
|
|
|||
|
|
if tier <= 15:
|
|||
|
|
return ["A", "B"]
|
|||
|
|
elif tier <= 30:
|
|||
|
|
return ["B", "C"]
|
|||
|
|
elif tier <= 45:
|
|||
|
|
return ["C", "D"]
|
|||
|
|
else:
|
|||
|
|
return ["D", "C"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def score_superpower(
|
|||
|
|
superpower: Dict[str, Any],
|
|||
|
|
glyph_id: str,
|
|||
|
|
metrics: Dict[str, Any],
|
|||
|
|
specialized_type: str = ""
|
|||
|
|
) -> float:
|
|||
|
|
"""Score a superpower for a glyph.
|
|||
|
|
|
|||
|
|
Formula: score = 0.45 × metrics + 0.35 × type_bias + 0.15 × boost% + 0.05 × hash
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
superpower: Superpower dict
|
|||
|
|
glyph_id: Glyph ID
|
|||
|
|
metrics: Glyph metrics
|
|||
|
|
specialized_type: Optional specialized type
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Score (0-100)
|
|||
|
|
"""
|
|||
|
|
# Metrics component (45%)
|
|||
|
|
avg_metric = sum(metrics.values()) / len(metrics) if metrics else 50
|
|||
|
|
metrics_score = avg_metric * 0.45
|
|||
|
|
|
|||
|
|
# Type bias component (35%)
|
|||
|
|
type_bias = 50 # Default neutral
|
|||
|
|
if specialized_type:
|
|||
|
|
type_config = get_type_config(specialized_type)
|
|||
|
|
preferred_ids = type_config.get("preferred_superpower_ids", [])
|
|||
|
|
if superpower["id"] in preferred_ids:
|
|||
|
|
type_bias = 100 # Preferred
|
|||
|
|
else:
|
|||
|
|
type_bias = 25 # Not preferred
|
|||
|
|
type_score = type_bias * 0.35
|
|||
|
|
|
|||
|
|
# Boost component (15%)
|
|||
|
|
boost = superpower.get("boost_percent", 0)
|
|||
|
|
boost_score = (boost / 100) * 0.15 * 100 # Normalize to 0-100 scale
|
|||
|
|
|
|||
|
|
# Hash component (5%) - deterministic variety
|
|||
|
|
hash_input = f"{glyph_id}_{superpower['id']}"
|
|||
|
|
hash_value = int(hashlib.md5(hash_input.encode()).hexdigest()[:8], 16)
|
|||
|
|
hash_score = (hash_value / 0xFFFFFFFF) * 100 * 0.05
|
|||
|
|
|
|||
|
|
return metrics_score + type_score + boost_score + hash_score
|
|||
|
|
|
|||
|
|
|
|||
|
|
def assign_superpowers(
|
|||
|
|
glyph_id: str,
|
|||
|
|
metrics: Dict[str, Any],
|
|||
|
|
specialized_type: str = "",
|
|||
|
|
category: str = ""
|
|||
|
|
) -> List[int]:
|
|||
|
|
"""Assign superpowers to a glyph.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
glyph_id: Glyph ID (e.g., "G001")
|
|||
|
|
metrics: Glyph metrics dict
|
|||
|
|
specialized_type: Optional specialized type
|
|||
|
|
category: Optional category
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
List of superpower IDs
|
|||
|
|
"""
|
|||
|
|
# Load superpowers if not loaded
|
|||
|
|
try:
|
|||
|
|
load_all_superpowers()
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
# If superpowers not loaded, return empty
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
# Special case: G001 / aether_node gets all 152
|
|||
|
|
if glyph_id == "G001" or specialized_type == "aether_node":
|
|||
|
|
return list(range(1, 153)) # 1-152
|
|||
|
|
|
|||
|
|
# Determine specialized type if not provided
|
|||
|
|
if not specialized_type:
|
|||
|
|
specialized_type = get_specialized_type(glyph_id, metrics, category)
|
|||
|
|
|
|||
|
|
# Calculate power count
|
|||
|
|
power_count = calculate_power_count(metrics, specialized_type)
|
|||
|
|
|
|||
|
|
# Get eligible bands
|
|||
|
|
eligible_bands = get_eligible_bands(glyph_id, specialized_type)
|
|||
|
|
|
|||
|
|
# Get eligible superpowers
|
|||
|
|
eligible_powers = get_superpowers_by_bands(eligible_bands)
|
|||
|
|
|
|||
|
|
# Score and rank
|
|||
|
|
scored = []
|
|||
|
|
for sp in eligible_powers:
|
|||
|
|
score = score_superpower(sp, glyph_id, metrics, specialized_type)
|
|||
|
|
scored.append((score, sp))
|
|||
|
|
|
|||
|
|
# Sort by score descending
|
|||
|
|
scored.sort(key=lambda x: x[0], reverse=True)
|
|||
|
|
|
|||
|
|
# Take top N
|
|||
|
|
result = [sp["id"] for score, sp in scored[:power_count]]
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
def assign_all_glyphs(glyphs: List[Dict[str, Any]]) -> Dict[str, List[int]]:
|
|||
|
|
"""Assign superpowers to all glyphs.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
glyphs: List of glyph dicts
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict mapping glyph_id to superpower IDs
|
|||
|
|
"""
|
|||
|
|
result = {}
|
|||
|
|
|
|||
|
|
for glyph in glyphs:
|
|||
|
|
glyph_id = glyph.get("id", "")
|
|||
|
|
metrics = glyph.get("originalMetrics", {})
|
|||
|
|
category = glyph.get("category", "")
|
|||
|
|
|
|||
|
|
# Get specialized type
|
|||
|
|
specialized_type = get_specialized_type(glyph_id, metrics, category)
|
|||
|
|
|
|||
|
|
# Assign superpowers
|
|||
|
|
superpower_ids = assign_superpowers(glyph_id, metrics, specialized_type, category)
|
|||
|
|
|
|||
|
|
result[glyph_id] = superpower_ids
|
|||
|
|
|
|||
|
|
return result
|