244 lines
7.0 KiB
Python
Executable File
244 lines
7.0 KiB
Python
Executable File
"""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 |