voxforge-world / engine /sim /material_profiles.py
peiti's picture
Upload folder using huggingface_hub
b154e4c verified
"""noctilith/sim/material_profiles.py — M13: VoxelDef → MaterialPipelineProfile."""
from __future__ import annotations
import math
from dataclasses import dataclass, field, replace
from typing import Any, Dict, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from engine.voxel_registry import VoxelDef
from engine.sim.material_degradation import MaterialDegradationConfig
from engine.sim.fracture import FractureBridgeConfig
from engine.sim.world_reaction import WorldReactionConfig
from engine.sim.debris_world_coupling import DebrisWorldCouplingConfig
SCHEMA_VERSION = "noctilith.schema.v1"
MODULE_NAME = "noctilith.sim.material_profiles"
def clamp(v: float, lo: float, hi: float) -> float:
return max(lo, min(hi, v))
@dataclass
class MaterialPipelineProfile:
voxel_name: str = "unknown"
voxel_id: int = -1
degradation: MaterialDegradationConfig = field(default_factory=MaterialDegradationConfig)
fracture: FractureBridgeConfig = field(default_factory=FractureBridgeConfig)
world_reaction: WorldReactionConfig = field(default_factory=WorldReactionConfig)
coupling: DebrisWorldCouplingConfig = field(default_factory=DebrisWorldCouplingConfig)
archetype: str = "generic"
thermal_fragility: float = 0.5
structural_index: float = 0.5
debris_tendency: float = 0.5
def summary(self) -> Dict[str, Any]:
return {
"schema_version": SCHEMA_VERSION,
"module_name": MODULE_NAME,
"voxel_name": self.voxel_name,
"voxel_id": self.voxel_id,
"archetype": self.archetype,
"thermal_fragility": float(self.thermal_fragility),
"structural_index": float(self.structural_index),
"debris_tendency": float(self.debris_tendency),
"fracture_threshold": float(self.fracture.fracture_risk_threshold),
"propagation_gain": float(self.fracture.propagation_gain),
"damage_gain": float(self.world_reaction.damage_gain),
"entropy_gain": float(self.coupling.entropy_gain),
"thermal_gain": float(self.coupling.thermal_gain),
"fatigue_exponent": float(self.degradation.fatigue_exponent),
}
def derive_from_voxel(vdef: "VoxelDef") -> MaterialPipelineProfile:
b = float(clamp(vdef.brittleness, 0.0, 1.0))
h = float(clamp(vdef.hardness, 0.0, 1.0))
por = float(clamp(vdef.porosity, 0.0, 1.0))
flm = float(clamp(vdef.flammability, 0.0, 1.0))
kth = float(max(1e-6, vdef.thermal_conductivity))
E = float(max(1.0, vdef.young_modulus))
Tm = float(max(1.0, vdef.melting_point_k))
k_norm = clamp(math.log10(kth + 1) / 2.5, 0.0, 1.0)
E_norm = clamp(math.log10(E + 1) / 12.0, 0.0, 1.0)
temp_w = clamp(0.25 + k_norm * 0.35, 0.25, 0.60)
entropy_w = clamp(0.20 + por * 0.55, 0.20, 0.75)
rms_w = clamp(1.0 - temp_w - entropy_w, 0.05, 0.40)
fat_exp = clamp(1.05 + E_norm * 0.55 + por * 0.25, 1.05, 1.85)
risk_gain = clamp(0.45 + b * 0.35 + por * 0.20, 0.45, 1.0)
degradation = replace(MaterialDegradationConfig(),
temp_weight=round(temp_w,4), entropy_weight=round(entropy_w,4),
rms_weight=round(rms_w,4), fatigue_exponent=round(fat_exp,4),
risk_gain=round(risk_gain,4))
frac_thresh = clamp(1.0 - b * 0.60, 0.35, 0.95)
prop_gain = clamp(b * 0.55 + 0.10, 0.10, 0.65)
prop_radius = 1 + (1 if b > 0.65 else 0)
fracture = replace(FractureBridgeConfig(),
fracture_risk_threshold=round(frac_thresh,4),
propagation_gain=round(prop_gain,4),
propagation_radius=prop_radius,
return_fracture_risk_field=True)
dmg_gain = clamp(1.0 - h * 0.65, 0.30, 0.90)
debris_thr = clamp(0.60 + h * 0.30, 0.60, 0.95)
dmg_thr = clamp(0.50 + h * 0.30, 0.50, 0.85)
world_reaction = replace(WorldReactionConfig(),
damage_gain=round(dmg_gain,4),
damage_threshold=round(dmg_thr,4),
debris_threshold=round(debris_thr,4))
ent_gain = clamp(0.20 + por * 0.65 + b * 0.15, 0.20, 1.0)
therm_gain = clamp(0.08 + k_norm * 0.25, 0.08, 0.35)
frag_thresh = clamp(1.0 - flm * 0.60, 0.35, 0.95)
frag_gain = clamp(0.20 + flm * 0.45, 0.20, 0.65)
coupling = replace(DebrisWorldCouplingConfig(),
entropy_gain=round(ent_gain,4), thermal_gain=round(therm_gain,4),
fragmentation_strength_threshold=round(frag_thresh,4),
fragmentation_strength_gain=round(frag_gain,4))
thermal_fragility = clamp(1.0 - math.log10(Tm+1)/4.5, 0.0, 1.0)
structural_index = clamp(E_norm*0.6 + h*0.4, 0.0, 1.0)
debris_tendency = clamp(b*0.5 + flm*0.3 + (1.0-h)*0.2, 0.0, 1.0)
archetype = _classify(b, h, por, flm)
return MaterialPipelineProfile(
voxel_name=str(vdef.name), voxel_id=int(vdef.id),
degradation=degradation, fracture=fracture,
world_reaction=world_reaction, coupling=coupling,
archetype=archetype,
thermal_fragility=round(thermal_fragility,4),
structural_index=round(structural_index,4),
debris_tendency=round(debris_tendency,4))
def _classify(b: float, h: float, por: float, flm: float) -> str:
if flm > 0.5: return "organic"
if por > 0.3: return "porous"
if b > 0.7 and h > 0.5: return "brittle"
if b < 0.3 and h > 0.5: return "ductile"
if b > 0.6 and h < 0.4: return "fused"
return "generic"
class MaterialProfileCache:
def __init__(self) -> None:
self._cache: Dict[int, MaterialPipelineProfile] = {}
def get(self, voxel_id: int, registry=None) -> Optional[MaterialPipelineProfile]:
if voxel_id in self._cache:
return self._cache[voxel_id]
if registry is None:
from engine.voxel_registry import REGISTRY
registry = REGISTRY
vdef = registry.by_id(voxel_id)
if vdef is None: return None
profile = derive_from_voxel(vdef)
self._cache[voxel_id] = profile
return profile
def get_by_name(self, name: str, registry=None) -> Optional[MaterialPipelineProfile]:
if registry is None:
from engine.voxel_registry import REGISTRY
registry = REGISTRY
vdef = registry.by_name(name)
if vdef is None: return None
return self.get(vdef.id, registry)
def warm_all(self, registry=None) -> int:
if registry is None:
from engine.voxel_registry import REGISTRY
registry = REGISTRY
for vid in registry.all_ids():
self.get(vid, registry)
return len(self._cache)
def clear(self) -> None:
self._cache.clear()
def __len__(self) -> int:
return len(self._cache)
PROFILE_CACHE = MaterialProfileCache()