Source code for mercurial.impressions.material_memory

"""Material memory effects: retention timescales and modality affinities (SPECTRAL A.4)."""

from dataclasses import dataclass
from enum import Enum
from typing import Dict

import numpy as np


[docs] class Material(Enum): QUARTZ = "quartz" IRON = "iron" LIMESTONE = "limestone" WOOD = "wood" WATER = "water" AIR = "air"
[docs] @dataclass class MaterialSpec: name: Material retention_timescale: float # τ_retain [seconds] modality_affinities: Dict[str, float] decay_exponent: float # α in I(t) = I₀ exp(-(t/τ)^α)
MATERIAL_SPECS = { Material.QUARTZ: MaterialSpec(Material.QUARTZ, 3.15e9, {"visual": 1.5, "affective": 1.3}, 0.8), Material.IRON: MaterialSpec(Material.IRON, 3.15e8, {"affective": 1.4, "auditory": 1.2}, 0.7), Material.LIMESTONE: MaterialSpec( Material.LIMESTONE, 3.15e10, {"auditory": 1.5, "visual": 1.3}, 0.6 ), Material.WOOD: MaterialSpec(Material.WOOD, 3.15e7, {"olfactory": 1.6, "tactile": 1.2}, 0.9), Material.WATER: MaterialSpec(Material.WATER, 86400, {}, 1.0), Material.AIR: MaterialSpec(Material.AIR, 3600, {}, 1.2), }
[docs] class MaterialMemory: def __init__(self, material: Material = Material.QUARTZ): self.material = material self.spec = MATERIAL_SPECS[material] self.impression_intensity = 0.0 # normalized 0..1 self.age = 0.0
[docs] def decay_factor(self, time: float) -> float: if time <= 0: return 1.0 return np.exp(-((time / self.spec.retention_timescale) ** self.spec.decay_exponent))
[docs] def amplify_modality(self, modality: str, raw_strength: float) -> float: gain = self.spec.modality_affinities.get(modality, 1.0) return raw_strength * gain
[docs] def update_impression(self, dt: float, influx_rate: float): """ dI/dt = influx_rate * (1 - I) - decay_rate * I This bounds I between 0 and 1. """ decay_rate = 1.0 / self.spec.retention_timescale # Saturation term: (1 - I) prevents exceeding 1 dI_dt = ( influx_rate * (1.0 - self.impression_intensity) - decay_rate * self.impression_intensity ) self.impression_intensity += dI_dt * dt self.impression_intensity = np.clip(self.impression_intensity, 0.0, 1.0) self.age += dt return self.impression_intensity
[docs] def get_retention_percentage(self, time: float) -> float: return self.decay_factor(time) * 100.0
[docs] class CompositeMaterial: def __init__(self, composition: Dict[Material, float]): self.composition = composition self.memories = {mat: MaterialMemory(mat) for mat in composition}
[docs] def update_all(self, dt: float, influx_rate: float): for mem in self.memories.values(): mem.update_impression(dt, influx_rate)
[docs] def amplify_modality(self, modality: str, raw_strength: float) -> float: total = 0.0 for mat, fraction in self.composition.items(): mem = self.memories[mat] total += fraction * mem.amplify_modality(modality, raw_strength) return total
[docs] def overall_impression_intensity(self) -> float: total = 0.0 for mat, fraction in self.composition.items(): total += fraction * self.memories[mat].impression_intensity return total