Spaces:
Sleeping
Sleeping
| """Fixed Physics Layer — computes VADUGWI from structural analysis. | |
| Pipeline: text -> classify words -> compute proximity -> detect structures -> apply physics -> VADUGWI | |
| The physics (momentum, force application, blending) are FIXED. | |
| The inputs come from the structural layers. | |
| Refactored into a modular pipeline: each stage is an independent function | |
| that reads from and writes to a shared context dict. Stages can be chained, | |
| bypassed, or swapped. | |
| """ | |
| import re | |
| from math import tanh, exp, log | |
| from typing import List, Optional, Tuple | |
| from .shared import VADUG, PersonalityVector | |
| from .word_classifier import WordRole, classify_sentence, _clean | |
| from .proximity import proximity_coefficient | |
| from .vocabulary import VOCABULARY | |
| from .structures import StructureDetector, StructureMatch | |
| from .force_flow import resolve_force_flow, compute_flow_modifiers, compute_intent | |
| from .phase import is_solvent, get_phase | |
| # ── Physics constants (fixed, never tuned per-sentence) ───────── | |
| CENTER = 128.0 | |
| M_BASE = 0.557 # champion v2: genetically optimized 2026-04-03 | |
| M_AROUSAL_SCALE = 0.25 # arousal-scaled momentum: high A = sticky state | |
| M_NEGATIVITY_BIAS = 1.15 # negative states are stickier than positive | |
| M_POSITIVITY_EASE = 0.90 # positive transitions are easier | |
| M_MIN = 0.30 # floor: never fully unresponsive to input | |
| M_MAX = 0.95 # ceiling: never fully locked in state | |
| FORCE_SCALE = 1.405 # champion v2 | |
| DIRECT_PUSH_CAP = 1.0 # champion v2: max push | |
| DIRECT_PUSH_TRIGGER = 86.2 # champion v2 | |
| SATURATION = 120.0 # tanh saturation: smooth compression replaces hard clamp | |
| # Mundane dampening: massless context atoms absorb crisis energy | |
| # D = (G_t + ε) / (G_t + α * |dV|) | |
| # Gemini's improvement: ε decays exponentially with |dV| so strong crisis | |
| # words near zero-gravity subjects get crushed almost to zero. | |
| # ε = e^(-λ|dV|) → at |dV|=35: ε=0.03, at |dV|=60: ε=0.002 | |
| MUNDANE_ALPHA = 0.04 # sensitivity: how much dV matters | |
| MUNDANE_EPSILON = 1.0 # floor: avoid division by zero (council suggested lower but ecosystem is tuned to this) | |
| MUNDANE_DV_THRESHOLD = 25 # only dampen high-charge atoms (crisis words) | |
| # ── Compound resolution tables ────────────────────────────────── | |
| _BOOKEND_COMPOUNDS = { | |
| "shut": "up", # shut [anything] up = silence command | |
| "get": "out", # get [the fuck] out = expulsion command | |
| "fuck": "off", # fuck [right] off = rejection command | |
| "back": "off", # back [the hell] off = distance command | |
| "piss": "off", # piss off = rejection | |
| } | |
| _SPICE_WORDS = {"the", "a", "an", "fuck", "fucking", "fuckin", "damn", | |
| "god", "hell", "mother", "motherfuckin", "stupid", | |
| "bitch", "ass", "right", "just", "already", "up", | |
| "freakin", "freaking", "effing"} | |
| _ONE_AS_QUANTIFIER = {"thing", "person", "place", "way", "reason", "time", | |
| "day", "moment", "word", "chance", "step", "bit"} | |
| _CONTINUATION_PAIRS = { | |
| ("wont", "stop"), ("wont", "quit"), ("wont", "end"), | |
| ("wont", "leave"), ("wont", "go"), | |
| ("cant", "stop"), ("cant", "quit"), | |
| ("dont", "stop"), ("doesnt", "stop"), | |
| ("never", "stop"), ("never", "stops"), ("never", "end"), | |
| ("never", "ends"), ("never", "quit"), | |
| } | |
| _UNIVERSAL_ADDRESS = { | |
| "everyone", "everybody", "anyone", "anybody", "all", "people", | |
| "folks", "y'all", "yall", "ladies", "gentlemen", | |
| } | |
| _SECOND_PERSON = {"you", "your", "yours", "yourself", "yourselves"} | |
| _ABSENCE_MARKERS = {"without", "havent", "haven't", "hasnt", "hasn't"} | |
| _ABSENCE_FOLLOWERS = {"had", "been", "felt", "seen", "gotten", "experienced"} | |
| _PERSON_ROLES = {"SELF_REF", "OTHER_REF", "RELATION_REF"} | |
| _CHOICE_VERBS = {"choose", "chose", "choosing", "decide", "pick", "picked"} | |
| # ── Trigram compound bonds (Council Round 8) ─────────────────── | |
| _COMPOUND_BONDS_TRI = { | |
| ("got", "laid", "off"): "laidoff", | |
| ("get", "laid", "off"): "laidoff", | |
| ("got", "kicked", "out"): "kickedout", | |
| ("got", "locked", "out"): "lockedout", | |
| ("got", "ripped", "off"): "rippedoff", | |
| ("got", "thrown", "out"): "thrownout", | |
| ("got", "cut", "off"): "cutoff", | |
| ("got", "burned", "out"): "burnedout", | |
| ("got", "wiped", "out"): "wipedout", | |
| } | |
| # ── Compound bond table (Council Round 8) ────────────────────── | |
| # Multi-word phrases that form molecular bonds with emergent charge. | |
| # Resolved in tokenizer → single atom → charge from vocabulary. | |
| _COMPOUND_BONDS = { | |
| # Negative life events | |
| ("laid", "off"): "laidoff", | |
| ("food", "poisoning"): "foodpoisoning", | |
| ("broke", "down"): "brokedown", | |
| ("locked", "out"): "lockedout", | |
| ("kicked", "out"): "kickedout", | |
| ("passed", "away"): "passedaway", | |
| ("cut", "off"): "cutoff", | |
| ("thrown", "out"): "thrownout", | |
| ("ripped", "off"): "rippedoff", | |
| ("wiped", "out"): "wipedout", | |
| ("burned", "out"): "burnedout", | |
| ("shut", "down"): "shutdown", | |
| ("backed", "out"): "backedout", | |
| ("dropped", "out"): "droppedout", | |
| ("sold", "out"): "soldout", | |
| ("stressed", "out"): "stressedout", | |
| ("freaked", "out"): "freakedout", | |
| ("ruled", "out"): "ruledout", | |
| ("washed", "out"): "washedout", | |
| ("checked", "out"): "checkedout", | |
| # Positive resolution events | |
| ("cancer", "free"): "cancerfree", | |
| ("debt", "free"): "debtfree", | |
| ("pain", "free"): "painfree", | |
| ("pulled", "off"): "pulledoff", | |
| ("pulled", "through"): "pulledthrough", | |
| ("worked", "out"): "workedout", | |
| ("paid", "off"): "paidoff", | |
| ("turned", "around"): "turnedaround", | |
| # Neutral/procedural compounds | |
| ("log", "in"): "login", | |
| ("sign", "up"): "signup", | |
| ("check", "in"): "checkin", | |
| ("pick", "up"): "pickup", | |
| ("set", "up"): "setup", | |
| ("WiFi", "down"): "wifidown", | |
| ("wifi", "down"): "wifidown", | |
| # Hype/intensifier compounds (slang batch 1) | |
| ("lets", "go"): "letsgo", | |
| ("let's", "go"): "letsgo", | |
| ("as", "hell"): "ashell", | |
| ("so", "back"): "soback", | |
| } | |
| # "w" reads as WIN only after these or sentence-finally; otherwise it's "with" | |
| _W_WIN_PRECEDERS = frozenset({ | |
| "a", "the", "big", "massive", "huge", "major", "another", | |
| "certified", "what", | |
| }) | |
| _ELONG_RUN = re.compile(r"([a-zA-Z])\1{2,}") | |
| _TRAP_GENRE_FOLLOWERS = frozenset({ | |
| "remix", "beat", "beats", "music", "song", "songs", "mix", | |
| "playlist", "banger", "instrumental", "album", "track", "tracks", | |
| }) | |
| _FIRE_HAZARD_PRECEDERS = frozenset({"a", "the"}) | |
| _FIRE_HAZARD_FOLLOWERS = frozenset({ | |
| "in", "at", "on", "alarm", "department", "drill", "spread", "broke", | |
| }) | |
| _FIRE_PRAISE_PRECEDERS = frozenset({ | |
| "is", "was", "its", "it's", "thats", "that's", "so", "straight", | |
| "absolute", "be", | |
| }) | |
| _FILTHY_SPORTS_NOUNS = frozenset({ | |
| "play", "move", "pitch", "dunk", "goal", "crossover", "mixtape", "beat", | |
| }) | |
| _MID_TEMPORAL_FOLLOWERS = frozenset({ | |
| "morning", "afternoon", "evening", "day", "week", "month", "year", | |
| "semester", "summer", "winter", "spring", "fall", "season", | |
| }) | |
| # ── Stage 1: Tokenize ────────────────────────────────────────── | |
| def tokenize(context: dict) -> dict: | |
| """Split text into words, resolve compound phrases and double negations. | |
| Reads: context["text"] | |
| Writes: context["words"] | |
| """ | |
| text = context["text"] | |
| words = text.split() | |
| if not words: | |
| context["words"] = [] | |
| return context | |
| # Bookend compounds: opener ... closer with variable filling | |
| collapsed = list(words) | |
| for opener, closer in _BOOKEND_COMPOUNDS.items(): | |
| start_idx = None | |
| for idx, w in enumerate(collapsed): | |
| if w.lower() == opener: | |
| start_idx = idx | |
| elif w.lower() == closer and start_idx is not None and idx - start_idx <= 8: | |
| filling = [collapsed[j].lower() for j in range(start_idx + 1, idx)] | |
| all_spice = all(f in _SPICE_WORDS for f in filling) | |
| if all_spice: | |
| compound = opener + closer | |
| collapsed = collapsed[:start_idx] + [compound] + collapsed[idx+1:] | |
| start_idx = None | |
| break | |
| words = collapsed | |
| # Double negation compounds and special pairs | |
| sentence_cleaned = {w.lower().rstrip(".,!?;:'\"") for w in words} | |
| resolved = [] | |
| i = 0 | |
| context.setdefault("elongation", False) | |
| while i < len(words): | |
| w_low = words[i].lower() | |
| next_low = words[i + 1].lower() if i + 1 < len(words) else "" | |
| pair = (w_low, next_low) | |
| # "w" sense split: WIN keeps its vocab charge; "with" carries no | |
| # force at all (a zero-force atom would still drain V toward center) | |
| if w_low.rstrip(".,!?;:'\"") == "w": | |
| prev_clean = words[i - 1].lower().rstrip(".,!?;:'\"") if i > 0 else "" | |
| if prev_clean in _W_WIN_PRECEDERS or i == len(words) - 1: | |
| resolved.append(words[i]) | |
| i += 1 | |
| continue | |
| # "extra" sense split: prenominal quantity ("extra cheese") carries | |
| # no charge; the slang sense ("she's so extra") keeps its vocab force | |
| if w_low.rstrip(".,!?;:'\"") == "extra" and next_low: | |
| nf = VOCABULARY.get(next_low.rstrip(".,!?;:'\"")) | |
| if nf is None or abs(nf[0]) < 10: | |
| i += 1 | |
| continue | |
| # "trap" sense split: music genre ("trap remix") is inert; the | |
| # entrapment sense keeps its vocab force | |
| if (w_low.rstrip(".,!?;:'\"") == "trap" | |
| and next_low.rstrip(".,!?;:'\"") in _TRAP_GENRE_FOLLOWERS): | |
| i += 1 | |
| continue | |
| # "fire" sense split: literal hazard vs predicate slang praise; | |
| # anything else keeps the (mild) stored vocab tuple | |
| if w_low.rstrip(".,!?;:'\"") == "fire": | |
| prev_clean = words[i - 1].lower().rstrip(".,!?;:'\"") if i > 0 else "" | |
| nxt_clean = next_low.rstrip(".,!?;:'\"") | |
| if prev_clean in _FIRE_HAZARD_PRECEDERS or nxt_clean in _FIRE_HAZARD_FOLLOWERS: | |
| resolved.append("firehazard") | |
| i += 1 | |
| continue | |
| if prev_clean in _FIRE_PRAISE_PRECEDERS: | |
| resolved.append("firepraise") | |
| i += 1 | |
| continue | |
| # "filthy" sense split: sports/performance praise when a sports | |
| # noun appears anywhere in the sentence | |
| if (w_low.rstrip(".,!?;:'\"") == "filthy" | |
| and not _FILTHY_SPORTS_NOUNS.isdisjoint(sentence_cleaned)): | |
| resolved.append("filthypraise") | |
| i += 1 | |
| continue | |
| # "mid" sense split: temporal compound ("mid morning") is inert; | |
| # the dismissive slang sense keeps its vocab force | |
| if (w_low.rstrip(".,!?;:'\"") == "mid" | |
| and next_low.rstrip(".,!?;:'\"") in _MID_TEMPORAL_FOLLOWERS): | |
| i += 1 | |
| continue | |
| if pair in _CONTINUATION_PAIRS: | |
| has_negative_before = any( | |
| VOCABULARY.get(words[j].lower(), (0,))[0] < -25 | |
| for j in range(max(0, i - 3), i) | |
| ) | |
| if has_negative_before: | |
| i += 2 | |
| else: | |
| resolved.append(words[i]) | |
| i += 1 | |
| elif w_low == "no" and next_low == "one": | |
| next_after = words[i + 2].lower() if i + 2 < len(words) else "" | |
| if next_after in _ONE_AS_QUANTIFIER: | |
| resolved.append(words[i]) | |
| i += 1 | |
| else: | |
| resolved.append("nobody") | |
| i += 2 | |
| elif w_low == "come" and next_low == "on": | |
| resolved.append("comeon") | |
| i += 2 | |
| elif w_low == "killed" and next_low == "it": | |
| resolved.append("killedit") | |
| i += 2 | |
| elif w_low == "goes" and next_low == "hard": | |
| resolved.append("goeshard") | |
| i += 2 | |
| elif w_low == "hit" and next_low == "different": | |
| resolved.append("hitdifferent") | |
| i += 2 | |
| elif w_low == "running" and next_low == "late": | |
| resolved.append("runninglate") | |
| i += 2 | |
| elif w_low == "made" and next_low == "it": | |
| # "made it" → achievement compound | |
| next_after = words[i + 2].lower() if i + 2 < len(words) else "" | |
| if next_after == "through": | |
| resolved.append("madeitthrough") | |
| i += 3 | |
| else: | |
| resolved.append("madeit") | |
| i += 2 | |
| elif w_low == "came" and next_low == "back": | |
| # Look ahead for "negative" → medical idiom (good news) | |
| next_after = words[i + 2].lower() if i + 2 < len(words) else "" | |
| if next_after == "negative": | |
| resolved.append("camebacknegative") | |
| i += 3 | |
| else: | |
| resolved.append(words[i]) | |
| i += 1 | |
| elif w_low == "closed" and next_low == "on": | |
| resolved.append("closedon") | |
| i += 2 | |
| elif w_low == "no" and next_low == "cap": | |
| resolved.append("nocap") | |
| i += 2 | |
| elif w_low == "no" and next_low == "way": | |
| resolved.append("noway") | |
| i += 2 | |
| else: | |
| # Council Round 8: compound bond resolution (bigram + trigram) | |
| w_clean = w_low.rstrip(".,!?;:'\"") | |
| n_clean = next_low.rstrip(".,!?;:'\"") | |
| # Elongation collapse: "GOOOOOO" -> "go" for vocab-missing tokens | |
| # so compound bonds and vocab lookups still land. Doubled form | |
| # is tried first because runs hide geminates: "GOOOOOD" must | |
| # collapse to "good", not "god" | |
| def _alt(tok): | |
| if tok and tok not in VOCABULARY and _ELONG_RUN.search(tok): | |
| two = _ELONG_RUN.sub(r"\1\1", tok) | |
| if two in VOCABULARY: | |
| return two | |
| return _ELONG_RUN.sub(r"\1", tok) | |
| return tok | |
| w_alt = _alt(w_clean) | |
| n_alt = _alt(n_clean) | |
| # Trigram check first | |
| if i + 2 < len(words): | |
| n2_clean = words[i + 2].lower().rstrip(".,!?;:'\"") | |
| tri = (w_clean, n_clean, n2_clean) | |
| if tri in _COMPOUND_BONDS_TRI: | |
| resolved.append(_COMPOUND_BONDS_TRI[tri]) | |
| i += 3 | |
| continue | |
| n2_alt = _alt(n2_clean) | |
| tri_alt = (w_alt, n_alt, n2_alt) | |
| if tri_alt != tri and tri_alt in _COMPOUND_BONDS_TRI: | |
| resolved.append(_COMPOUND_BONDS_TRI[tri_alt]) | |
| context["elongation"] = True | |
| i += 3 | |
| continue | |
| # Bigram check | |
| bi = (w_clean, n_clean) | |
| bi_alt = (w_alt, n_alt) | |
| if bi in _COMPOUND_BONDS: | |
| resolved.append(_COMPOUND_BONDS[bi]) | |
| i += 2 | |
| elif bi_alt != bi and bi_alt in _COMPOUND_BONDS: | |
| resolved.append(_COMPOUND_BONDS[bi_alt]) | |
| context["elongation"] = True | |
| i += 2 | |
| elif w_alt != w_clean and w_alt in VOCABULARY: | |
| resolved.append(w_alt) | |
| context["elongation"] = True | |
| i += 1 | |
| else: | |
| resolved.append(words[i]) | |
| i += 1 | |
| words = resolved | |
| context["words"] = words | |
| return context | |
| # ── Stage 2: Classify ────────────────────────────────────────── | |
| def classify(context: dict) -> dict: | |
| """Classify words into structural roles, apply perspective remapping. | |
| Reads: context["words"], context["perspective"], context.get("personality") | |
| Writes: context["roles"], context["perspective"] (may be resolved from "auto"), | |
| context["has_universal"] | |
| """ | |
| words = context["words"] | |
| perspective = context.get("perspective", "speaker") | |
| personality = context.get("personality") | |
| roles = classify_sentence(words) | |
| # Universal address detection | |
| has_universal = any(w.lower() in _UNIVERSAL_ADDRESS for w in words) | |
| context["has_universal"] = has_universal | |
| # Auto-detect perspective | |
| if perspective == "auto": | |
| has_self = any(wr.role == "SELF_REF" for wr in roles) | |
| has_second = any(wr.word.lower() in _SECOND_PERSON for wr in roles) | |
| if has_self: | |
| perspective = "speaker" | |
| elif has_second: | |
| perspective = "listener" | |
| elif has_universal: | |
| perspective = "listener" | |
| else: | |
| perspective = "bystander" | |
| # Perspective remapping | |
| if perspective == "listener": | |
| for wr in roles: | |
| if wr.role == "SELF_REF": | |
| wr.role = "OTHER_REF" | |
| elif wr.role == "OTHER_REF": | |
| wr.role = "SELF_REF" | |
| elif perspective == "bystander": | |
| if has_universal: | |
| for wr in roles: | |
| if wr.role == "SELF_REF": | |
| wr.role = "OTHER_REF" | |
| else: | |
| for wr in roles: | |
| if wr.role in ("SELF_REF", "OTHER_REF"): | |
| wr.role = "NEUTRAL" | |
| # Bystander self-projection | |
| if perspective == "bystander" and personality is not None: | |
| bystander_w = personality.assertiveness | |
| if bystander_w < 80: | |
| _THIRD_PERSON = {"she", "he", "her", "him", "they", "them"} | |
| for idx, wr in enumerate(roles): | |
| if wr.word.lower() in _THIRD_PERSON and wr.role == "NEUTRAL": | |
| for j in range(max(0, idx - 3), min(len(roles), idx + 4)): | |
| if j != idx and roles[j].role == "EMOTIONAL": | |
| w_clean = _clean(roles[j].word) | |
| forces = VOCABULARY.get(w_clean) | |
| if forces and forces[0] < -10: | |
| wr.role = "SELF_REF" | |
| break | |
| # Absence scope | |
| absence_scope = set() | |
| for i, wr in enumerate(roles): | |
| if wr.word in _ABSENCE_MARKERS: | |
| if wr.word == "without": | |
| for j in range(i + 1, min(i + 4, len(roles))): | |
| if roles[j].role not in _PERSON_ROLES: | |
| absence_scope.add(j) | |
| elif i + 1 < len(roles) and roles[i + 1].word in _ABSENCE_FOLLOWERS: | |
| for j in range(i + 2, min(i + 7, len(roles))): | |
| absence_scope.add(j) | |
| # Forced choice cancellation | |
| forced_choice_scope = set() | |
| for i, wr in enumerate(roles): | |
| if wr.word == "between": | |
| has_choice = any(roles[j].word in _CHOICE_VERBS | |
| for j in range(max(0, i - 3), i)) | |
| if has_choice: | |
| for j in range(i + 1, min(i + 6, len(roles))): | |
| forced_choice_scope.add(j) | |
| context["roles"] = roles | |
| context["perspective"] = perspective | |
| context["absence_scope"] = absence_scope | |
| context["forced_choice_scope"] = forced_choice_scope | |
| return context | |
| # ── Stage 2.5: Interpret Context ────────────────────────────── | |
| # GPT's insight: "words → role/context interpretation → forces → result" | |
| # This layer reclassifies roles based on PRAGMATIC FUNCTION before | |
| # forces are computed. The physics is the same — the interpretation | |
| # of what role each atom plays changes based on molecular context. | |
| _DISCOURSE_AFFIRMS = frozenset({"no", "nah", "nope"}) | |
| _DISCOURSE_LOOKAHEAD_POS = frozenset({ | |
| "good", "fine", "right", "great", "cool", "ok", "okay", | |
| "way", "cap", "doubt", "kidding", "worries", | |
| }) | |
| _DISCOURSE_SELF_TOKENS = frozenset({"i", "im", "i'm", "ive", "i've"}) | |
| _LIQUID_LITERAL_FOLLOWERS = { | |
| "cooked": frozenset({"dinner", "breakfast", "lunch", "food", "meal", | |
| "meals", "rice", "chicken", "steak", "pasta", | |
| "eggs"}), | |
| } | |
| _EXPLETIVE_WORDS = frozenset({"shit", "fuck", "damn", "hell", "goddamn"}) | |
| _COUNTERFACTUAL_MARKERS = frozenset({ | |
| "supposed", "would", "should", "could", "wished", "hoped", | |
| }) | |
| _PAST_TRUST_VERBS = frozenset({ | |
| "trusted", "believed", "thought", "assumed", "expected", | |
| }) | |
| _INSTRUCTIONAL_CUES = frozenset({ | |
| "please", "proceed", "enter", "select", "follow", | |
| "click", "press", "navigate", "submit", "confirm", | |
| "objective", "quest", "mission", "instructions", | |
| }) | |
| def interpret_context(context: dict) -> dict: | |
| """Reinterpret word roles based on pragmatic context. | |
| Runs AFTER classify, BEFORE force computation. Modifies roles | |
| and sets context flags that downstream stages use. | |
| Fixes: | |
| 1. Discourse markers: "no we good" → "no" becomes DISCOURSE_AFFIRM | |
| 2. Expletive-as-intensifier: "shit you are right" → "shit" becomes AMPLIFIER | |
| 3. Register detection: instructional text → force dampening flag | |
| 4. Counterfactual marking: "supposed to" → positive forces inverted | |
| 5. Hard negator inversion: NEGATOR + strong positive → full sign flip | |
| """ | |
| roles = context["roles"] | |
| if not roles: | |
| return context | |
| words = [r.word for r in roles] | |
| n = len(roles) | |
| # ── 1. Discourse markers ────────────────────────────────── | |
| # "no we good" — "no" followed by positive content = affirm, not negate | |
| for i, wr in enumerate(roles): | |
| if wr.word in _DISCOURSE_AFFIRMS and wr.role == "NEGATOR": | |
| # Check 3-token lookahead for positive signal | |
| lookahead = words[i+1:i+4] | |
| has_positive_ahead = any(w in _DISCOURSE_LOOKAHEAD_POS for w in lookahead) | |
| # Also check: is there a positive emotional word ahead? | |
| has_emo_pos = any( | |
| roles[j].force and roles[j].force[0] > 10 | |
| for j in range(i+1, min(i+4, n)) | |
| ) | |
| if has_positive_ahead or has_emo_pos: | |
| # Reclassify: this "no" is discourse, not negation | |
| wr.role = "FILLER" | |
| wr.base_role = "FILLER" | |
| elif i == 0 and n > 1 and roles[1].word in _DISCOURSE_SELF_TOKENS: | |
| # "no, i'm sick" — sentence-initial answer-marker before a | |
| # self-statement negates the (implied) question, not the | |
| # clause that follows | |
| wr.role = "FILLER" | |
| wr.base_role = "FILLER" | |
| # ── 2. Expletive-as-intensifier ─────────────────────────── | |
| # "shit you are right" — sentence-initial expletive + positive/affirm content | |
| _AFFIRM_WORDS = frozenset({ | |
| "right", "true", "yes", "exactly", "correct", "agreed", | |
| "good", "great", "nice", "thanks", "thank", | |
| }) | |
| # Rescue sites: sentence-initial, or right after a demonstrative | |
| # ("This shit slaps" — the demonstrative marks the expletive as subject | |
| # stand-in, not invective) | |
| _DEMONSTRATIVES = frozenset({"this", "that", "the"}) | |
| for idx in range(n): | |
| if roles[idx].word not in _EXPLETIVE_WORDS: | |
| continue | |
| if idx != 0 and roles[idx - 1].word not in _DEMONSTRATIVES: | |
| continue | |
| # Check if next clause has positive content OR affirmative words | |
| pos_ahead = sum( | |
| 1 for j in range(idx + 1, min(idx + 6, n)) | |
| if (roles[j].force and roles[j].force[0] > 10) | |
| or roles[j].word in _AFFIRM_WORDS | |
| ) | |
| if pos_ahead > 0: | |
| # Convert expletive to amplifier. The explicit zero tuple (not | |
| # None) blocks every downstream `force or VOCABULARY.get(word)` | |
| # fallback from re-resolving the expletive's negative charge | |
| roles[idx].role = "AMPLIFIER" | |
| roles[idx].force = (0, 0, 0, 0, 0) | |
| # ── 2b. Degree-adverb slang ─────────────────────────────── | |
| # "stupid good" / "insanely talented" — the negative adjective is a | |
| # degree marker on the positive word, not its own charge | |
| _DEGREE_SLANG = frozenset({"stupid", "dumb", "insanely", "ridiculously", | |
| "stupidly"}) | |
| for idx in range(n - 1): | |
| if roles[idx].word not in _DEGREE_SLANG: | |
| continue | |
| nf = roles[idx + 1].force or VOCABULARY.get(roles[idx + 1].word) | |
| if nf and nf[0] >= 20: | |
| roles[idx].role = "AMPLIFIER" | |
| roles[idx].force = (0, 0, 0, 0, 0) | |
| # ── 3. Register detection (Council Round 7) ──────────────── | |
| # CONVERSATIONAL = full force (speaker emitting charge) | |
| # LITERARY = dampened force (charge is described/reported, not felt) | |
| # EXPOSITORY = heavily dampened (procedural/instructional text) | |
| # | |
| # Detection via Dielectric Index (Gemini) + agency structure (GPT): | |
| # High article/3rd-person density + no 1st/2nd person + no casual markers = narration | |
| # The medium is dense — weak atoms scatter, strong atoms punch through dampened. | |
| instructional_count = sum(1 for w in words if w in _INSTRUCTIONAL_CUES) | |
| _CASUAL_SIGNALS = {"im", "ive", "youre", "dont", "cant", "wont", | |
| "gonna", "wanna", "gotta", "lol", "lmao", "bruh", "bro", | |
| "dude", "omg", "tbh", "ngl", "fr", "nah", "yeah", "yep", | |
| "hey", "yo", "haha", "ok", "okay"} | |
| _ARTICLES = {"the", "a", "an", "this", "these", "those"} | |
| _THIRD_PERSON = {"he", "she", "they", "it", "his", "her", "its", "their", | |
| "him", "them"} | |
| _FIRST_PERSON = {"i", "im", "ive", "my", "me", "we", "us", "our"} | |
| _SECOND_PERSON = {"you", "your", "youre"} | |
| _LITERARY_VERBS = {"seized", "seizing", "walked", "strode", "gazed", | |
| "muttered", "exclaimed", "remarked", "observed", | |
| "whispered", "cried", "replied", "declared"} | |
| casual_count = sum(1 for w in words if w in _CASUAL_SIGNALS) | |
| has_exclamation = any(w.endswith('!') for w in words) | |
| has_quotes = context["text"].count('"') >= 2 | |
| c_art = sum(1 for w in words if w in _ARTICLES) | |
| c_3rd = sum(1 for w in words if w in _THIRD_PERSON) | |
| c_1st = sum(1 for w in words if w in _FIRST_PERSON) | |
| c_2nd = sum(1 for w in words if w in _SECOND_PERSON) | |
| c_lit_verb = sum(1 for w in words if w in _LITERARY_VERBS) | |
| past_count = sum(1 for w in words if w.endswith("ed") or w in {"was", "were", "had", "been"}) | |
| # Dielectric Index: (articles + 3rd person) / total tokens | |
| dx = (c_art + c_3rd) / max(1, n) | |
| past_ratio = past_count / max(1, n) | |
| # Observation score (GPT): agency-based | |
| obs_score = dx + 0.3 * past_ratio + (0.1 if casual_count == 0 else 0) | |
| # Literary verb bonus (Claude) | |
| if c_lit_verb >= 1: | |
| obs_score += 0.2 | |
| # Decision hierarchy: | |
| # 1. Instructional cues → EXPOSITORY (strongest override) | |
| # 2. Casual markers or exclamation or quotes → CONVERSATIONAL | |
| # 3. High observation score + no 1st/2nd person + length >= 5 → LITERARY | |
| # 4. Default → CONVERSATIONAL | |
| if instructional_count >= 1: | |
| register = "EXPOSITORY" | |
| context["register_dampener"] = 0.35 | |
| elif casual_count >= 1 or has_exclamation or has_quotes: | |
| register = "CONVERSATIONAL" | |
| context["register_dampener"] = 1.0 | |
| elif obs_score >= 0.50 and c_1st == 0 and c_2nd == 0 and casual_count == 0 and n >= 10: | |
| register = "LITERARY" | |
| # Gemini's mass-dependent scattering: applied per-word in accumulate_forces | |
| context["register_dampener"] = 0.55 | |
| else: | |
| register = "CONVERSATIONAL" | |
| context["register_dampener"] = 1.0 | |
| context["register"] = register | |
| # ── 4. Counterfactual marking ───────────────────────────── | |
| # "supposed to", "would have" → flag for force inversion | |
| has_counterfactual = any(w in _COUNTERFACTUAL_MARKERS for w in words) | |
| has_past_trust = any(w in _PAST_TRUST_VERBS for w in words) | |
| context["counterfactual"] = has_counterfactual | |
| context["past_trust"] = has_past_trust | |
| # ── 5. Hard negator inversion ───────────────────────────── | |
| # NEGATOR within 3 tokens of positive EMOTIONAL (dV > 25) → flip sign | |
| # Also reclassify the NEGATOR to FILLER so proximity doesn't double-negate | |
| negators_consumed = set() | |
| for i, wr in enumerate(roles): | |
| if wr.role == "NEGATOR": | |
| for j in range(i+1, min(n, i+4)): | |
| jr = roles[j] | |
| f = jr.force or VOCABULARY.get(jr.word) | |
| if f and f[0] > 25: | |
| # Full inversion: flip the positive word's dV | |
| old_f = f | |
| jr.force = (-old_f[0], old_f[1], -old_f[2], old_f[3], old_f[4]) | |
| negators_consumed.add(i) | |
| break | |
| # Also consume negators that precede a communication/action verb | |
| # before distant emotional content. | |
| # "nobody tells you grief" → "nobody" negates "tells", not "grief" | |
| # "nobody loves me" → "nobody" SHOULD negate "loves" → don't consume | |
| # Key: consume only when intervening word is a low-charge comm verb | |
| _COMM_VERBS = frozenset({ | |
| "tells", "told", "says", "said", "asks", "asked", | |
| "knows", "knew", "thinks", "thought", "warns", "warned", | |
| "prepares", "prepared", "expects", "expected", | |
| "mentions", "mentioned", "explains", "explained", | |
| }) | |
| if i not in negators_consumed: | |
| for j in range(i+1, min(n, i+3)): | |
| if roles[j].word in _COMM_VERBS: | |
| negators_consumed.add(i) | |
| break | |
| # Consumed negators become FILLER so proximity doesn't double-apply | |
| for i in negators_consumed: | |
| roles[i].role = "FILLER" | |
| # ── 6. SOLVENT dissolution ──────────────────────────────── | |
| # SOLVENT words (bruh, lol, lmao, dude, etc.) dissolve LIQUID atoms | |
| # "bruh im shook" → SOLVENT(bruh) flips LIQUID(shook) from negative to positive | |
| # "bruh he got murdered" → SOLVENT can't dissolve SOLID(murdered) | |
| # Literal-verb guard: a LIQUID slang verb followed by its concrete | |
| # object ("cooked dinner") or preceded by "home" is the literal sense | |
| # — neutral, not the stored slang-doom charge, and not dissolvable | |
| for idx, wr in enumerate(roles): | |
| followers = _LIQUID_LITERAL_FOLLOWERS.get(wr.word) | |
| if not followers: | |
| continue | |
| nxt = roles[idx + 1].word if idx + 1 < n else "" | |
| prev = roles[idx - 1].word if idx > 0 else "" | |
| if nxt in followers or prev == "home": | |
| wr.force = (0, 0, 0, 0, 0) | |
| has_solvent = any(is_solvent(wr.word) for wr in roles) | |
| if has_solvent: | |
| for wr in roles: | |
| if get_phase(wr.word) == "LIQUID": | |
| # Get the force (may be on wr.force or in VOCABULARY) | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f and f[0] < -5: | |
| # Flip dV sign, keep arousal (the energy stays, charge flips) | |
| flipped = (-f[0], f[1], abs(f[2]), f[3], abs(f[4])) | |
| wr.force = flipped | |
| context["solvent_active"] = True | |
| else: | |
| context["solvent_active"] = False | |
| # ── 7. Sarcasm inversion field (Council Round 6) ──────────── | |
| # Genuine enthusiasm radiates amplification energy. Sarcasm is a | |
| # cold molecule — positive surface with zero kinetic energy. | |
| # Ironic onset + tepid positive + zero amplifiers → invert. | |
| _IRONIC_ONSETS = frozenset({ | |
| "clearly", "oh", "wow", "sure", "right", "great", | |
| "nice", "yeah", "gee", "wonderful", "brilliant", "lovely", | |
| }) | |
| _AMPLIFIERS = frozenset({ | |
| "so", "really", "very", "super", "extremely", "absolutely", | |
| "honestly", "seriously", "genuinely", "totally", | |
| }) | |
| first_word = words[0] if words else "" | |
| two_word = " ".join(words[:2]) if len(words) >= 2 else "" | |
| has_ironic_onset = first_word in _IRONIC_ONSETS or two_word in ("what a", "oh great", "oh cool", "oh nice") | |
| has_amplifier = any(w in _AMPLIFIERS for w in words) or any(w.endswith("!") for w in words) | |
| if has_ironic_onset and not has_amplifier and not has_solvent: | |
| # Count all charged atoms | |
| total_charge = 0 | |
| pos_count = 0 | |
| neg_count = 0 | |
| for wr in roles: | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f: | |
| total_charge += f[0] | |
| if f[0] > 0: | |
| pos_count += 1 | |
| elif f[0] < 0: | |
| neg_count += 1 | |
| # Flat affect: ironic onset + mostly near-zero/mildly negative atoms | |
| # + no strong positive to anchor genuine enthusiasm | |
| # This catches: "clearly this was well thought out" (all atoms -5 or 0) | |
| # "oh cool cant wait for that" (all atoms -5 to -10) | |
| if neg_count >= pos_count and total_charge < 0 and neg_count >= 1: | |
| # All atoms weakly negative + ironic onset = sarcasm | |
| # Apply a structural V penalty | |
| context["sarcasm_inversion"] = True | |
| context["sarcasm_penalty"] = -15.0 # applied in apply_structures | |
| # ── 7b. Contrast sarcasm: strong positive + negative in same sentence ── | |
| # "I am just overjoyed to clean up your mess" — "overjoyed" near "mess" | |
| # "Thanks for that incredibly useless advice" — "thanks" near "useless" | |
| # The CONTRAST between positive and negative in a short span = sarcasm | |
| if not context.get("sarcasm_inversion") and not has_solvent: | |
| strong_pos = [] | |
| strong_neg = [] | |
| for wr in roles: | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f: | |
| if f[0] >= 15: | |
| strong_pos.append(wr.word) | |
| elif f[0] <= -15: | |
| strong_neg.append(wr.word) | |
| # Both strong positive AND strong negative = contrast sarcasm | |
| if strong_pos and strong_neg and len(words) <= 15: | |
| context["sarcasm_inversion"] = True | |
| context["sarcasm_penalty"] = -12.0 | |
| context["roles"] = roles | |
| return context | |
| # ── Stage 3: Compute Coefficients (structure detection + force flow) ─ | |
| def compute_coefficients(context: dict) -> dict: | |
| """Detect structures and resolve force flow. | |
| Reads: context["roles"] | |
| Writes: context["structures"], context["force_flow"], context["flow_mods"] | |
| """ | |
| roles = context["roles"] | |
| detector = StructureDetector() | |
| structures = detector.detect_all(roles) | |
| force_flow = resolve_force_flow(roles) | |
| flow_mods = compute_flow_modifiers(force_flow) | |
| context["structures"] = structures | |
| context["force_flow"] = force_flow | |
| context["flow_mods"] = flow_mods | |
| return context | |
| # ── Stage 4: Accumulate Forces ────────────────────────────────── | |
| def accumulate_forces(context: dict) -> dict: | |
| """Per-word force application loop with adaptive momentum. | |
| Reads: context["roles"], context["absence_scope"], context["forced_choice_scope"], | |
| context["force_flow"], context["flow_mods"], | |
| context["register_dampener"], context["counterfactual"], context["past_trust"] | |
| Writes: context["state_v"], context["state_a"], context["state_d"], | |
| context["state_u"], context["state_g"], context["state_w"], | |
| context["trace_entries"] | |
| """ | |
| roles = context["roles"] | |
| absence_scope = context.get("absence_scope", set()) | |
| forced_choice_scope = context.get("forced_choice_scope", set()) | |
| force_flow = context.get("force_flow") | |
| flow_mods = context.get("flow_mods", {}) | |
| state_v = CENTER | |
| state_a = CENTER | |
| state_d = CENTER | |
| state_u = 0.0 | |
| state_g = CENTER | |
| state_w = CENTER | |
| trace_entries: List[dict] = [] | |
| for i, wr in enumerate(roles): | |
| if wr.role == "POSSESSION": | |
| vf = VOCABULARY.get(wr.word) | |
| if vf: | |
| word_force = (0, 0, 0, 0, max(5, vf[4])) | |
| else: | |
| word_force = (0, 0, 0, 0, 5) | |
| else: | |
| word_force = wr.force | |
| if word_force is None: | |
| word_force = VOCABULARY.get(wr.word) | |
| if word_force is None: | |
| from .fuzzy import fuzzy_match | |
| matched = fuzzy_match(wr.word) | |
| if matched: | |
| word_force = VOCABULARY.get(matched) | |
| # A zero-charge tuple on a connector/temporal/amplifier word | |
| # carries no information — treating it as a hit would drain | |
| # accumulated deviation back toward center, punishing sentences | |
| # for containing function words. Only above center: positive | |
| # deviation is protected from erosion, while below center the | |
| # drain keeps safe text neutral (mirrors M_NEGATIVITY_BIAS / | |
| # M_POSITIVITY_EASE asymmetry). | |
| _INERT_ZERO_ROLES = {"CONNECTOR", "TEMPORAL", "FILLER", | |
| "AMPLIFIER"} | |
| if (word_force is not None | |
| and state_v >= CENTER | |
| and wr.role in _INERT_ZERO_ROLES | |
| and word_force[0] == 0 and word_force[1] == 0 | |
| and word_force[2] == 0 and word_force[3] == 0 | |
| and abs(word_force[4]) <= 5): | |
| word_force = None | |
| # Same protection for near-zero register markers: a trailing | |
| # "ngl"/"fr"/"tbh" is punctuation-grade, not a counter-force | |
| if (word_force is not None | |
| and state_v >= CENTER | |
| and (wr.role == "REGISTER_CASUAL" or is_solvent(wr.word)) | |
| and abs(word_force[0]) <= 5 and abs(word_force[1]) <= 5 | |
| and abs(word_force[2]) <= 5 and word_force[3] == 0 | |
| and abs(word_force[4]) <= 5): | |
| word_force = None | |
| if word_force is None: | |
| trace_entries.append({ | |
| "word": wr.word, | |
| "role": wr.role, | |
| "coeff": 0.0, | |
| "v": round(state_v), | |
| "a": round(state_a), | |
| "d": round(state_d), | |
| "u": round(state_u), | |
| "g": round(state_g), | |
| "w": round(state_w), | |
| }) | |
| continue | |
| dv, da, dd, du, dg = word_force | |
| # ── REGISTER DAMPENING ── | |
| # LITERARY: Gemini's mass-dependent scattering — weak atoms scatter, | |
| # strong atoms punch through dampened. dV_eff = dV * (1 - e^(-k|dV|)) * ε | |
| # EXPOSITORY: flat dampening (instructional text) | |
| reg_damp = context.get("register_dampener", 1.0) | |
| if reg_damp < 1.0: | |
| if context.get("register") == "LITERARY": | |
| # Mass-dependent scattering: weak atoms crushed, strong survive | |
| scatter = 1.0 - exp(-0.05 * abs(dv)) # approaches 1.0 for strong atoms | |
| dv = int(dv * scatter * reg_damp) | |
| da = int(da * scatter * reg_damp) | |
| dd = int(dd * scatter * reg_damp) | |
| else: | |
| # Flat dampening for EXPOSITORY | |
| dv = int(dv * reg_damp) | |
| da = int(da * reg_damp) | |
| dd = int(dd * reg_damp) | |
| # ── COUNTERFACTUAL INVERSION: positive in past/counterfactual → grief ── | |
| if context.get("counterfactual") and dv > 10: | |
| dv = int(dv * -0.75) # 75% inversion | |
| elif context.get("past_trust") and dv > 5: | |
| dv = int(dv * 0.5) # dampen positive (broken trust context) | |
| # ── MUNDANE DAMPENING: massless context absorbs crisis energy ── | |
| # High-charge emotional atoms (|dV| > threshold) get dampened when | |
| # the sentence's subject/agent is a mundane noun (low gravity). | |
| # "homework is killing me" — subject=homework (G=0) → DAMPEN | |
| # "i want to kill myself" — subject=I (SELF_REF) → PRESERVE | |
| # The subject is the first substantive noun before the crisis verb, | |
| # skipping connectors, determiners, and filler. | |
| if abs(dv) >= MUNDANE_DV_THRESHOLD and wr.role == "EMOTIONAL": | |
| _AGENTIC_ROLES = {"SELF_REF", "OTHER_REF", "RELATION_REF"} | |
| _SKIP_ROLES = {"CONNECTOR", "NEUTRAL", "FILLER", "TEMPORAL", | |
| "AMPLIFIER", "NEGATOR", "COMPRESSOR", "HEDGE"} | |
| # Scan backward for the first substantive word (subject) | |
| agent_g = None | |
| agent_is_person = False | |
| for j in range(i - 1, max(-1, i - 8), -1): | |
| jr = roles[j] | |
| if jr.role in _AGENTIC_ROLES: | |
| agent_is_person = True | |
| break | |
| # Skip function words — look for the actual subject noun | |
| if jr.role in _SKIP_ROLES: | |
| jf = jr.force or VOCABULARY.get(jr.word) | |
| # Word with no force at all = maximally mundane (G=0) | |
| if jf is None: | |
| agent_g = 0 | |
| break | |
| # Word with low charge + low gravity = mundane subject | |
| if abs(jf[0]) < 10 and abs(jf[4]) < 15: | |
| agent_g = abs(jf[4]) | |
| break | |
| continue | |
| if jr.role in ("EMOTIONAL", "POSSESSION"): | |
| jf = jr.force or VOCABULARY.get(jr.word) | |
| jg = abs(jf[4]) if jf else 0 | |
| agent_g = jg | |
| break | |
| # Only dampen if subject is mundane (not a person, low G) | |
| if not agent_is_person and agent_g is not None and agent_g < 15: | |
| D = (agent_g + MUNDANE_EPSILON) / (agent_g + MUNDANE_ALPHA * abs(dv)) | |
| dv = int(dv * D) | |
| da = int(da * D) | |
| # Forced choice cancellation | |
| if i in forced_choice_scope and dv > 0: | |
| dv = -dv | |
| # Absence scope dampening | |
| if i in absence_scope and dv < -10: | |
| dv = int(dv * 0.2) | |
| da = int(da * 0.3) | |
| dd = int(dd * 0.3) | |
| # "Without" as pure operator | |
| if wr.word in ("without",) and any(j in absence_scope for j in range(i+1, min(i+4, len(roles)))): | |
| dv = 0 | |
| da = 0 | |
| dd = 0 | |
| # Force flow direction modifiers | |
| if i == (force_flow.force_idx if force_flow else -1): | |
| dv = int(dv * flow_mods["v_mod"]) | |
| dd = int(dd * flow_mods["d_mod"]) | |
| coeff = proximity_coefficient(roles, i) | |
| # Target = center + force * coefficient * scale | |
| target_v = CENTER + dv * coeff * FORCE_SCALE | |
| target_a = CENTER + da * coeff * FORCE_SCALE | |
| target_d = CENTER + dd * coeff * FORCE_SCALE | |
| target_u = du * abs(coeff) * FORCE_SCALE | |
| target_g = CENTER + dg * coeff * FORCE_SCALE | |
| # Direct push for strong forces | |
| total_force = abs(dv) + abs(da) + abs(dd) + abs(du) + abs(dg) | |
| push_strength = min(1.0, total_force / DIRECT_PUSH_TRIGGER) * DIRECT_PUSH_CAP | |
| push_v = push_strength * (1.0 if dv * coeff >= 0 else -1.0) * abs(dv) * FORCE_SCALE | |
| push_a = push_strength * (1.0 if da * coeff >= 0 else -1.0) * abs(da) * FORCE_SCALE | |
| push_d = push_strength * (1.0 if dd * coeff >= 0 else -1.0) * abs(dd) * FORCE_SCALE | |
| push_u = push_strength * abs(du) * FORCE_SCALE | |
| push_g = push_strength * (1.0 if dg * coeff >= 0 else -1.0) * abs(dg) * FORCE_SCALE | |
| # Adaptive momentum | |
| m_eff = M_BASE + (state_a - CENTER) / 255.0 * M_AROUSAL_SCALE | |
| if state_v < CENTER and target_v > state_v: | |
| m_v = max(M_MIN, min(M_MAX, m_eff * M_NEGATIVITY_BIAS)) | |
| elif state_v > CENTER and target_v < state_v: | |
| m_v = max(M_MIN, min(M_MAX, m_eff * M_POSITIVITY_EASE)) | |
| else: | |
| m_v = max(M_MIN, min(M_MAX, m_eff)) | |
| m_eff = max(M_MIN, min(M_MAX, m_eff)) | |
| inv_m_v = 1.0 - m_v | |
| inv_m = 1.0 - m_eff | |
| inv_m_base = 1.0 - M_BASE | |
| state_v = state_v * m_v + target_v * inv_m_v + push_v | |
| state_a = state_a * m_eff + target_a * inv_m + push_a | |
| state_d = state_d * m_eff + target_d * inv_m + push_d | |
| state_u = state_u * M_BASE + target_u * inv_m_base + push_u | |
| state_g = state_g * M_BASE + target_g * inv_m_base + push_g | |
| # W (self-worth): valence routed through attribution (Board 2). | |
| # R answers "how much of this force's valence is about MY worth?" | |
| # At the force word, R comes from the force-flow arc when it resolved | |
| # real entity tokens: | |
| # self-declarative ("i am worthless") R = w_mod (1.5 neg / 0.7 pos) | |
| # force aimed at self ("he told me im nothing") R = w_mod (1.2 neg / 0.9 pos) | |
| # self harms other ("i hurt her") — guilt R = 0.43 (x0.7 damp ≈ 0.30) | |
| # other-directed / atmospheric, no self token R = 0 (W untouched — | |
| # "the weather is awful" says nothing about MY worth) | |
| # Elsewhere (and when the flow resolved nothing or only implied | |
| # entities) W falls back to the legacy SELF_REF-proximity gate. | |
| self_ref_nearby = any( | |
| roles[j].role == "SELF_REF" and abs(j - i) <= 4 | |
| for j in range(max(0, i - 4), min(len(roles), i + 5)) | |
| if j != i | |
| ) | |
| w_damp = 0.7 | |
| w_flow = flow_mods["w_mod"] if force_flow and i == force_flow.force_idx else 1.0 | |
| w_apply = self_ref_nearby | |
| if force_flow is not None and i == force_flow.force_idx and dv != 0: | |
| _a_role = force_flow.actor_role | |
| _t_role = force_flow.target_role | |
| _actor_token = force_flow.actor_idx >= 0 | |
| _target_token = force_flow.target_idx >= 0 | |
| _self_token_involved = ((_a_role == "SELF_REF" and _actor_token) | |
| or (_t_role == "SELF_REF" and _target_token)) | |
| if (_a_role == "SELF_REF" and _actor_token | |
| and _t_role in ("OTHER_REF", "RELATION_REF") and _target_token | |
| and dv < 0): | |
| # Guilt: self as actor of a negative act toward another. | |
| # Partial self-attribution — your worth dips, but the | |
| # valence belongs mostly to what you did, not what you are. | |
| w_flow = 0.43 | |
| w_apply = True | |
| elif _self_token_involved: | |
| # Self-declarative or self-targeted force: route the | |
| # valence into W even when the SELF token sits outside | |
| # the 4-word proximity window. | |
| w_apply = True | |
| if w_apply and dv != 0: | |
| w_effective = dv * coeff * FORCE_SCALE * w_damp * w_flow | |
| target_w = CENTER + w_effective | |
| push_w = push_strength * (1.0 if dv * coeff >= 0 else -1.0) * abs(dv) * FORCE_SCALE * w_damp * w_flow | |
| state_w = state_w * m_eff + target_w * inv_m + push_w | |
| trace_entries.append({ | |
| "word": wr.word, | |
| "role": wr.role, | |
| "coeff": round(coeff, 3), | |
| "v": round(state_v), | |
| "a": round(state_a), | |
| "d": round(state_d), | |
| "u": round(state_u), | |
| "g": round(state_g), | |
| "w": round(state_w), | |
| }) | |
| # ── BIDIRECTIONAL CORRECTION (sentence-level A+B=C) ───────────── | |
| # Instead of a full backward pass, pre-scan for the strongest emotional | |
| # atom. If it's in the second half of the sentence AND the forward pass | |
| # didn't reach its polarity, apply a correction push. | |
| # This fixes "I just got laid off from work" where the event "laidoff" | |
| # is in the middle but momentum recovery from trailing neutral words | |
| # erases its charge. | |
| strongest_dv = 0 | |
| strongest_pos = 0 | |
| mid = len(roles) // 2 | |
| for i, wr in enumerate(roles): | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f and abs(f[0]) > abs(strongest_dv): | |
| strongest_dv = f[0] | |
| strongest_pos = i | |
| # If strongest atom is past midpoint and forward V disagrees with its polarity | |
| if strongest_pos >= mid and abs(strongest_dv) >= 20: | |
| fwd_dev = state_v - CENTER | |
| atom_direction = 1 if strongest_dv > 0 else -1 | |
| fwd_direction = 1 if fwd_dev > 0 else -1 if fwd_dev < 0 else 0 | |
| if atom_direction != fwd_direction or abs(fwd_dev) < abs(strongest_dv) * 0.3: | |
| # Forward pass didn't capture the event's polarity — apply correction | |
| correction = strongest_dv * 0.3 * FORCE_SCALE | |
| state_v += correction | |
| context["state_v"] = state_v | |
| context["state_a"] = state_a | |
| context["state_d"] = state_d | |
| context["state_u"] = state_u | |
| context["state_g"] = state_g | |
| context["state_w"] = state_w | |
| context["trace_entries"] = trace_entries | |
| return context | |
| # ── Stage 5: Apply Structures ────────────────────────────────── | |
| def apply_structures(context: dict) -> dict: | |
| """Apply structure detection adjustments to state. | |
| Reads: context["state_*"], context["structures"], context["roles"] | |
| Writes: context["state_v"], context["state_d"], context["state_u"], | |
| context["state_g"], context["state_w"] | |
| """ | |
| structures = context.get("structures", []) | |
| roles = context.get("roles", []) | |
| state_v = context["state_v"] | |
| state_a = context["state_a"] | |
| state_d = context["state_d"] | |
| state_u = context["state_u"] | |
| state_g = context["state_g"] | |
| state_w = context["state_w"] | |
| for sm in structures: | |
| if sm.pattern == "SLANG_DEATH_HUMOR": | |
| # Nullify death word's negative dV and add back a positive-scaled version. | |
| # The death word is NOT functioning as death -- it's an intensifier. | |
| # V_corrected = V_raw - dV("dead") + 0.7 * abs(dV("dead")) | |
| death_dv_total = 0 | |
| for idx in sm.matched_indices: | |
| if idx < len(roles): | |
| w = roles[idx].word | |
| vf = VOCABULARY.get(w) | |
| if vf and vf[0] < -10: # negative death word | |
| death_dv_total += vf[0] | |
| if death_dv_total < 0: | |
| # Subtract the death word's accumulated negative push and add positive version. | |
| # Use 1.2x on the nullification to account for momentum/push amplification | |
| # during accumulation, and 0.7x for the positive reinterpretation. | |
| correction = 1.2 * abs(death_dv_total) * FORCE_SCALE + 0.7 * abs(death_dv_total) * FORCE_SCALE | |
| state_v += correction * sm.confidence | |
| else: | |
| # Fallback: pull toward center | |
| distance = CENTER - state_v | |
| state_v += distance * 1.2 * sm.confidence | |
| state_w = max(state_w, CENTER) | |
| elif sm.pattern == "AMBIGUITY_HOLD": | |
| # Extreme V contradiction with no disambiguator: pull V toward W (neutral baseline). | |
| # V_final = V + (W - V) * 0.85 | |
| state_v = state_v + (state_w - state_v) * 0.85 * sm.confidence | |
| elif sm.pattern == "RECOVERY_MILESTONE": | |
| # Recovery milestone: apply v_weight as direct positive boost | |
| state_v += sm.v_weight * sm.confidence * FORCE_SCALE | |
| elif sm.pattern == "HEDGED_ASSESSMENT": | |
| # A trailing hedge ("i guess", "i suppose") DAMPS conviction; | |
| # it does not add negative content. The hedge word's raw vocab | |
| # force (e.g. "guess" V-21) already accumulated as if it were | |
| # content -- refund it (1.3x for momentum amplification), then | |
| # apply the structure's mild damp. | |
| hedge_dv = 0.0 | |
| hedge_dw = 0.0 | |
| for idx in sm.matched_indices: | |
| if idx < len(roles): | |
| hf = roles[idx].force or VOCABULARY.get(roles[idx].word) | |
| if hf: | |
| if hf[0] < 0: | |
| hedge_dv += hf[0] | |
| if len(hf) > 4 and hf[4] < 0: | |
| hedge_dw += hf[4] | |
| if hedge_dv < 0: | |
| state_v += 1.3 * abs(hedge_dv) * FORCE_SCALE | |
| if hedge_dw < 0: | |
| state_w += 1.3 * abs(hedge_dw) * FORCE_SCALE | |
| state_v += sm.v_weight * sm.confidence * FORCE_SCALE | |
| elif sm.pattern in ("SARCASM_INVERSION", "BRAVADO", "DIRECTED_POSITIVE", "EXCLUDED_POSITIVE", "GRIEF_LOSS", "ATMOSPHERIC_GRIEF", "RHETORICAL_SELF_NEGATION", "REPORTED_COMFORT", "PASSIVE_RESIGNATION", "MASKING", "TEMPORAL_GRIEVANCE", "EXCLUSION_CONTRAST", "IRONIC_DEFERENCE", "FAINT_PRAISE", "RETROSPECTIVE_HOPE") and state_v > CENTER: | |
| excess = state_v - CENTER | |
| pull = sm.v_weight * sm.confidence * FORCE_SCALE * (1.0 + excess / 50.0) | |
| state_v += pull | |
| if sm.pattern == "RHETORICAL_SELF_NEGATION" and state_w > CENTER: | |
| w_excess = state_w - CENTER | |
| w_pull = sm.w_weight * sm.confidence * FORCE_SCALE * (1.0 + w_excess / 50.0) | |
| state_w += w_pull | |
| sm = StructureMatch( | |
| pattern=sm.pattern, confidence=sm.confidence, | |
| matched_indices=sm.matched_indices, description=sm.description, | |
| v_weight=sm.v_weight, d_weight=sm.d_weight, u_weight=sm.u_weight, | |
| g_weight=sm.g_weight, w_weight=0.0, | |
| ) | |
| elif sm.pattern == "CHOPPER_SPLIT" and sm.matched_indices: | |
| chop_pos = sm.matched_indices[0] | |
| after_words = [wr for wr in roles if wr.position > chop_pos] | |
| after_v_sum = 0 | |
| for wr in after_words: | |
| wf = wr.force or VOCABULARY.get(wr.word) | |
| if wf: | |
| after_v_sum += wf[0] | |
| has_negator_after = any(wr.role == "NEGATOR" for wr in after_words) | |
| if (state_v > CENTER and (after_v_sum < 0 or (after_v_sum == 0 and has_negator_after))): | |
| distance = state_v - CENTER | |
| state_v -= distance * 1.5 * sm.confidence | |
| elif (state_v < CENTER and after_v_sum > 10): | |
| distance = CENTER - state_v | |
| state_v += distance * 0.4 * sm.confidence | |
| else: | |
| state_v += sm.v_weight * sm.confidence * FORCE_SCALE | |
| state_a += sm.a_weight * sm.confidence * FORCE_SCALE | |
| state_d += sm.d_weight * sm.confidence * FORCE_SCALE | |
| state_u += sm.u_weight * sm.confidence * FORCE_SCALE | |
| state_g += sm.g_weight * sm.confidence * FORCE_SCALE | |
| state_w += sm.w_weight * sm.confidence * FORCE_SCALE | |
| # Compound bond event anchoring (Council Round 8) | |
| # If a compound bond exists, it's the EVENT NUCLEUS — anchor V toward its charge | |
| _COMPOUND_VOCAB_KEYS = set(_COMPOUND_BONDS.values()) | set(_COMPOUND_BONDS_TRI.values()) | |
| for wr in roles: | |
| if wr.word in _COMPOUND_VOCAB_KEYS: | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f and abs(f[0]) >= 25: | |
| # Anchor: push V toward the compound's charge direction | |
| anchor_push = f[0] * 0.4 * FORCE_SCALE | |
| state_v += anchor_push | |
| # Sarcasm inversion penalty (from interpret_context step 7) | |
| sarcasm_penalty = context.get("sarcasm_penalty", 0.0) | |
| if sarcasm_penalty != 0.0: | |
| state_v += sarcasm_penalty * FORCE_SCALE | |
| context["state_v"] = state_v | |
| context["state_a"] = state_a | |
| context["state_d"] = state_d | |
| context["state_u"] = state_u | |
| context["state_g"] = state_g | |
| context["state_w"] = state_w | |
| return context | |
| # ── Stage 5b: Static Friction (Council Round 7) ────────────────── | |
| # Gemini's model: if no atom has |dV| > threshold, the sentence lacks | |
| # emotional conviction. The pendulum can't overcome static friction. | |
| # V deviation from center is dampened by (|dV_max| / threshold)². | |
| STATIC_FRICTION_THRESHOLD = 20 # minimum |dV| to overcome friction (15 was too aggressive) | |
| def apply_static_friction(context: dict) -> dict: | |
| """Prevent weak negative/positive drift from accumulated noise. | |
| If no word in the sentence has |dV| > threshold, the total V deviation | |
| is dampened by a squared ratio. Stronger max atoms = less dampening. | |
| Exempted when crisis structures fire (dangling bonds etc. have zero-charge | |
| atoms but structural signals that override). | |
| """ | |
| state_v = context["state_v"] | |
| roles = context.get("roles", []) | |
| structures = context.get("structures", []) | |
| # Find max absolute dV in the sentence | |
| max_abs_dv = 0 | |
| for wr in roles: | |
| f = wr.force or VOCABULARY.get(wr.word) | |
| if f: | |
| max_abs_dv = max(max_abs_dv, abs(f[0])) | |
| # Exempt if crisis structures fired | |
| _CRISIS_STRUCTS = {"DANGLING_BOND", "FAREWELL", "MASKING", "RESIGNATION", | |
| "WORLD_CONTINUES", "FINALITY", "METHOD_ACQUISITION", | |
| "SELF_REMOVAL", "SUSPICIOUS_CALM", "SELF_HARM_INTENT", | |
| "EXHAUSTION", "NO_EXIT"} | |
| has_crisis_struct = any(sm.pattern in _CRISIS_STRUCTS for sm in structures) | |
| if max_abs_dv < STATIC_FRICTION_THRESHOLD and not has_crisis_struct: | |
| # Squared ratio: smooth transition, harder to move with weaker atoms | |
| friction = (max_abs_dv / STATIC_FRICTION_THRESHOLD) ** 2 | |
| deviation = state_v - CENTER | |
| state_v = CENTER + deviation * friction | |
| context["state_v"] = state_v | |
| return context | |
| # ── Stage 6: Apply W Coefficient ──────────────────────────────── | |
| def apply_w_coefficient(context: dict) -> dict: | |
| """Self-worth modulates valence via asymmetric exponential coupling. | |
| Low W amplifies negative V (validation of broken state). | |
| Low W suppresses positive V (rejection of contradictory energy). | |
| At W=128 (neutral), negatives are amplified 1.5x (calibrated | |
| negativity bias — the whole benchmark suite is fitted around it; | |
| making neutral-W a no-op was tested 2026-06-11 and regressed | |
| stress 271→263, crisis recall 49→46). At W=50, ~1.8x (capped). | |
| GPT's equation: | |
| w = (W - 128) / 128 → normalized: -1 (broken) to +1 (strong) | |
| V_neg' = V_neg * (1 + β * e^(-w)) → amplify negatives when w < 0 | |
| V_pos' = V_pos * (1 - γ * (1 - e^w)) → suppress positives when w < 0 | |
| Reads: context["state_v"], context["state_w"] | |
| Writes: context["state_v"] | |
| """ | |
| state_v = context["state_v"] | |
| state_w = context["state_w"] | |
| # Normalized self-worth: -1 = broken, 0 = neutral, +1 = strong | |
| w_norm = (state_w - CENTER) / CENTER | |
| W_BETA = 0.5 # negative amplification strength (was 0.8, too aggressive) | |
| W_GAMMA = 0.3 # positive suppression strength (was 0.6, killed slang positives) | |
| W_CAP = 1.8 # max amplification | |
| displacement = state_v - CENTER | |
| if displacement < 0: | |
| # Negative V: amplify when W is low (w_norm < 0 → e^(-w_norm) > 1) | |
| amp = min(W_CAP, 1.0 + W_BETA * exp(-w_norm)) | |
| state_v = CENTER + displacement * amp | |
| elif displacement > 0: | |
| # Positive V: suppress when W is low (w_norm < 0 → e^(w_norm) < 1) | |
| sup = max(0.0, 1.0 - W_GAMMA * (1.0 - exp(w_norm))) | |
| state_v = CENTER + displacement * sup | |
| context["state_v"] = state_v | |
| return context | |
| # ── Stage 7: Apply Personality ────────────────────────────────── | |
| def apply_personality(context: dict) -> dict: | |
| """Scale state by personality vector if provided. | |
| Reads: context["state_*"], context.get("personality") | |
| Writes: context["state_*"] | |
| """ | |
| personality = context.get("personality") | |
| if personality is None: | |
| return context | |
| state_v = context["state_v"] | |
| state_a = context["state_a"] | |
| state_d = context["state_d"] | |
| state_u = context["state_u"] | |
| state_g = context["state_g"] | |
| state_w = context["state_w"] | |
| sensitivity = personality.emotional_sensitivity | |
| state_v = CENTER + (state_v - CENTER) * sensitivity | |
| state_a = CENTER + (state_a - CENTER) * sensitivity | |
| state_d = CENTER + (state_d - CENTER) * sensitivity + personality.dominance_baseline | |
| state_u = state_u * sensitivity | |
| state_g = CENTER + (state_g - CENTER) * sensitivity + personality.gravity_bias | |
| state_w = CENTER + (state_w - CENTER) * sensitivity | |
| context["state_v"] = state_v | |
| context["state_a"] = state_a | |
| context["state_d"] = state_d | |
| context["state_u"] = state_u | |
| context["state_g"] = state_g | |
| context["state_w"] = state_w | |
| return context | |
| # ── Stage 8: Saturate and Clamp ───────────────────────────────── | |
| def saturate_and_clamp(context: dict) -> dict: | |
| """Tanh saturation, intent computation, and 0-255 clamping. | |
| Reads: context["state_*"], context["force_flow"], context["roles"], | |
| context["trace_entries"], context["structures"], context["words"] | |
| Writes: context["vadug"], context["meta"] | |
| """ | |
| state_v = context["state_v"] | |
| state_a = context["state_a"] | |
| state_d = context["state_d"] | |
| state_u = context["state_u"] | |
| state_g = context["state_g"] | |
| state_w = context["state_w"] | |
| force_flow = context.get("force_flow") | |
| roles = context.get("roles", []) | |
| # Intent computation | |
| state_i = compute_intent(force_flow, roles) | |
| # Tier-1 passive aggression lifts I toward CONTROL: the speaker is | |
| # fighting, deniably — the embedded grievance is a directed jab, not | |
| # neutral information. Only lift out of the neutral/connect band; a | |
| # strong directional reading (withdraw/attack) is never overridden. | |
| _PA_CONTROL_PATTERNS = { | |
| "TEMPORAL_GRIEVANCE", "EXCLUSION_CONTRAST", | |
| "IRONIC_DEFERENCE", "FAINT_PRAISE", "RETROSPECTIVE_HOPE", | |
| } | |
| structures = context.get("structures", []) | |
| if any(sm.pattern in _PA_CONTROL_PATTERNS for sm in structures) and \ | |
| 90 <= state_i < 168: | |
| state_i = 168 | |
| # Tanh saturation | |
| state_v = CENTER + SATURATION * tanh((state_v - CENTER) / SATURATION) | |
| state_a = CENTER + SATURATION * tanh((state_a - CENTER) / SATURATION) | |
| state_d = CENTER + SATURATION * tanh((state_d - CENTER) / SATURATION) | |
| state_u = SATURATION * tanh(state_u / SATURATION) | |
| state_g = CENTER + SATURATION * tanh((state_g - CENTER) / SATURATION) | |
| state_w = CENTER + SATURATION * tanh((state_w - CENTER) / SATURATION) | |
| # Clamp to 0-255 | |
| result = VADUG( | |
| v=int(round(max(0, min(255, state_v)))), | |
| a=int(round(max(0, min(255, state_a)))), | |
| d=int(round(max(0, min(255, state_d)))), | |
| u=int(round(max(0, min(255, state_u)))), | |
| g=int(round(max(0, min(255, state_g)))), | |
| w=int(round(max(0, min(255, state_w)))), | |
| i=state_i, | |
| ) | |
| trace_dict = { | |
| "trace": context.get("trace_entries", []), | |
| "structures": context.get("structures", []), | |
| "force_flow": force_flow, | |
| "word_count": len(context.get("words", [])), | |
| } | |
| context["vadug"] = result | |
| context["meta"] = trace_dict | |
| return context | |
| # ── Pipeline ──────────────────────────────────────────────────── | |
| class Pipeline: | |
| """Chainable pipeline of stage functions. | |
| Each stage is a function(context: dict) -> dict. | |
| Stages read from and write to the shared context dict. | |
| """ | |
| def __init__(self, stages=None): | |
| self.stages = stages if stages is not None else self.default_stages() | |
| def default_stages(): | |
| return [ | |
| tokenize, | |
| classify, | |
| interpret_context, # V8.1: role reinterpretation before forces | |
| compute_coefficients, | |
| accumulate_forces, | |
| apply_structures, | |
| # apply_static_friction, # Council R7: needs higher threshold tuning, disabled for now | |
| apply_w_coefficient, | |
| apply_personality, | |
| saturate_and_clamp, | |
| ] | |
| def run(self, text: str, perspective: str = "speaker", | |
| personality: Optional[PersonalityVector] = None) -> Tuple[VADUG, dict]: | |
| """Run the full pipeline on text. | |
| Returns (VADUG, trace_dict) — same interface as compute_vadug(). | |
| """ | |
| context = { | |
| "text": text, | |
| "perspective": perspective, | |
| "personality": personality, | |
| } | |
| # Early exit for empty text | |
| words = text.split() | |
| if not words: | |
| return VADUG(), {"trace": [], "structures": [], "word_count": 0} | |
| for stage in self.stages: | |
| context = stage(context) | |
| return context["vadug"], context["meta"] | |
| # ── Main entry point (thin wrapper) ──────────────────────────── | |
| def compute_vadug( | |
| text: str, | |
| personality: Optional[PersonalityVector] = None, | |
| perspective: str = "speaker", | |
| ) -> Tuple[VADUG, dict]: | |
| """Compute VADUGWI coordinates for a text string. | |
| Pipeline: | |
| 1. Split text into words | |
| 2. Layer 1: classify_sentence() -- structural roles | |
| 3. Layer 2: proximity_coefficient() -- distance-based influence | |
| 4. Layer 3: StructureDetector().detect_all() -- chess-like patterns | |
| 5. Physics loop: momentum + force blending | |
| 6. Structure adjustments | |
| 7. Personality adjustments (if provided) | |
| 8. Clamp to 0-255 | |
| perspective controls whose emotional state is being scored: | |
| - "speaker": default. "I" = self, "you" = other. Scores the speaker. | |
| - "listener": "you" = self, "I" = other. Scores the person being spoken to. | |
| - "bystander": no self. "I" and "you" are both other people. Scores a | |
| detached observer who sees the emotional content but takes no directed hits. | |
| Returns (VADUG, trace_dict) where trace_dict contains: | |
| - trace: list of per-word entries {word, role, coeff, v, a, d, u, g} | |
| - structures: list of detected StructureMatch objects | |
| - word_count: int | |
| """ | |
| return Pipeline().run(text, perspective=perspective, personality=personality) | |