"""V3 Layer 2: Proximity Weighting — distance-based influence fields. Words are like celestial bodies — they have mass (emotional weight). Placing them near each other creates gravitational effects. "give" near "dog" near "neighbor" creates a farewell field. "give" far from "dog" = probably unrelated. The proximity field computes how much each word influences every other word based on DISTANCE. Closer = stronger. Influence decays exponentially: influence = PROXIMITY_DECAY ^ distance This layer answers: "Which words are pulling on this word, and how hard?" """ from typing import Dict, List, Tuple from .word_classifier import WordRole # ── Constants ──────────────────────────────────────────────────── PROXIMITY_DECAY = 0.90 # champion v2: wide influence range INFLUENCE_CUTOFF = 0.1 # ignore influence below this COEFFICIENT_CAP = 2.63 # champion v2: tighter cap prevents runaway # ── Role modifier strengths (champion v2, genetically tuned 2026-04-03) ── ROLE_MODIFIERS = { "AMPLIFIER": 0.965, # champion v2: amplifiers hit hard "NEGATOR": -2.464, # champion v2: strong negation flip "SELF_REF": 0.466, # champion v2: self-reference personalizes "HEDGE": -0.416, # champion v2: hedges dampen more than v1 "COMPRESSOR": -0.301, # champion v2: compression "REGISTER_CASUAL": 0.0, # SOLVENT: phase-dependent handling in modifier block } # ── Core functions ─────────────────────────────────────────────── def compute_proximity_field( roles: List[WordRole], ) -> Dict[int, Dict[int, float]]: """Compute influence of every word on every other word. Returns {word_idx: {other_idx: influence_strength}} where influence is PROXIMITY_DECAY ^ distance. Only includes pairs with influence above INFLUENCE_CUTOFF. """ n = len(roles) field: Dict[int, Dict[int, float]] = {} for i in range(n): influences: Dict[int, float] = {} for j in range(n): if i == j: continue distance = abs(i - j) influence = PROXIMITY_DECAY ** distance if influence >= INFLUENCE_CUTOFF: influences[j] = influence field[i] = influences return field def find_role_pairs( roles: List[WordRole], role_a: str, role_b: str, max_distance: int = 5, ) -> List[Tuple[int, int, float]]: """Find all pairs of specific roles within proximity. Returns [(idx_a, idx_b, proximity_strength)] sorted by strength descending (strongest first). """ indices_a = [r.position for r in roles if r.role == role_a] indices_b = [r.position for r in roles if r.role == role_b] pairs: List[Tuple[int, int, float]] = [] for ia in indices_a: for ib in indices_b: distance = abs(ia - ib) if distance == 0 or distance > max_distance: continue strength = PROXIMITY_DECAY ** distance pairs.append((ia, ib, strength)) pairs.sort(key=lambda p: p[2], reverse=True) return pairs def proximity_coefficient( roles: List[WordRole], target_idx: int, ) -> float: """Compute combined proximity coefficient for a word. Nearby modifier roles (AMPLIFIER, NEGATOR, SELF_REF, HEDGE) adjust the coefficient multiplicatively. The result is capped to [-COEFFICIENT_CAP, +COEFFICIENT_CAP]. Returns a float coefficient (1.0 = no modification). """ if not roles or target_idx < 0 or target_idx >= len(roles): return 1.0 coeff = 1.0 n = len(roles) for i in range(n): if i == target_idx: continue role = roles[i].role distance = abs(i - target_idx) influence = PROXIMITY_DECAY ** distance if influence < INFLUENCE_CUTOFF: continue # Operator modifiers (amplifier, negator, self-ref, hedge) if role in ROLE_MODIFIERS: modifier = ROLE_MODIFIERS[role] # Some words resist negation: # EXPLETIVES: "no fuck you" stays negative # DECEPTION verbs: "pretended not to" = the "not" belongs to the next verb # "not pretended" doesn't make sense. The deception is real. _NEGATION_RESISTANT = { "fuck", "shit", "damn", "hell", "ass", "bitch", "bastard", "crap", "dick", "piss", "pretended", "pretending", "faked", "faking", "lied", "lying", } if role == "NEGATOR" and roles[target_idx].force: if roles[target_idx].word in _NEGATION_RESISTANT: modifier *= 0.15 # barely any negation # Compressor dome doesn't touch people — only values/emotions. # "only" + self = neutral isolation. The rest of the sentence # decides if that isolation is proud or lonely. _PERSON_ROLES = {"SELF_REF", "OTHER_REF", "RELATION_REF"} if role == "COMPRESSOR" and roles[target_idx].role in _PERSON_ROLES: modifier = 0.0 # dome passes through people # SOLVENT: REGISTER_CASUAL dissolves LIQUID atoms, can't dissolve SOLID # "bruh im crying" → crying flips to positive # "bruh he got murdered" → murdered stays negative if role == "REGISTER_CASUAL" and roles[target_idx].force: from .phase import get_phase phase = get_phase(roles[target_idx].word) target_dv = roles[target_idx].force[0] if phase == "LIQUID" and target_dv < 0: # Dissolve NEGATIVE liquid → flip to positive modifier = -2.0 * influence elif phase == "LIQUID" and target_dv > 0: # POSITIVE liquid near solvent → amplify (already positive) modifier = 0.4 * influence elif phase == "SOLID": modifier = 0.0 # can't dissolve rock else: # GAS modifier = 0.3 * influence coeff *= (1.0 + modifier * influence) # Star-to-star gravity: stronger emotional words pull weaker ones # Uses EMOTIONAL DISTANCE (skip neutral/connector words between stars) # "cheated on me with my best" -- emotional distance cheated->best = 1 # Connectors/neutrals are transparent conduits, not walls if role == "EMOTIONAL" and roles[i].force and roles[target_idx].force: their_v = roles[i].force[0] my_v = roles[target_idx].force[0] if abs(their_v) > abs(my_v) * 1.5: # Count only EMOTIONAL words between them for distance lo, hi = min(i, target_idx), max(i, target_idx) emo_between = sum(1 for k in range(lo+1, hi) if roles[k].role == "EMOTIONAL") emo_distance = max(1, emo_between + 1) emo_influence = PROXIMITY_DECAY ** emo_distance mass_ratio = abs(their_v) / max(abs(my_v), 1) pull = emo_influence * min(mass_ratio * 0.15, 0.8) if their_v < 0: coeff *= (1.0 - pull) else: coeff *= (1.0 + pull * 0.5) # Relationship amplification: nearby RELATION_REF amplifies negative forces # Wife(G=40) near cheated(-127) = betrayal hits harder because trust was higher # The relationship G value IS the trust level -- higher trust = bigger fall # # Determiner check: "my mother" = possessive bond (full G). # "the mother" = distanced/clinical (dampened G). The article severs # the gravitational bond between speaker and relationship. _ARTICLES = {"the", "a", "an"} target_role = roles[target_idx] if target_role.force and target_role.force[0] < -20: # negative emotional word for i in range(n): if i == target_idx: continue if roles[i].role == "RELATION_REF": distance = abs(i - target_idx) influence = PROXIMITY_DECAY ** distance if influence < INFLUENCE_CUTOFF: continue # Get the relationship G value from vocabulary from .vocabulary import VOCABULARY rel_g = 20 # default if roles[i].word in VOCABULARY: rel_g = max(5, VOCABULARY[roles[i].word][4]) # "the/a mother" = distanced, dampen G contribution # "my mother" = bonded, full G if i > 0 and roles[i - 1].word in _ARTICLES: rel_g = int(rel_g * 0.3) # 70% reduction -- article severs bond # Amplify: higher relationship G = bigger betrayal multiplier betrayal_mult = (rel_g / 20.0) * influence coeff *= (1.0 + betrayal_mult * 0.3) return max(-COEFFICIENT_CAP, min(COEFFICIENT_CAP, coeff))