clanker-hackathon / engine /force_flow.py
deucebucket's picture
clanker hackathon gradio entry (verification copy)
ecca2a3 verified
Raw
History Blame Contribute Delete
16.6 kB
"""V4 Layer 1.5: Force Flow Resolver — WHO does WHAT to WHOM.
Every sentence has a force flow: an actor pushes force toward a target.
The direction determines how the impact lands.
"I love my dog" → Self --love(+)--> pet (giving affection)
"He hit me" → Other --hit(-)--> Self (victimization)
"I hit him" → Self --hit(-)--> Other (aggression, D stays)
"The medicine stopped working" → thing --stopped--> function (purpose severed)
The resolver identifies Subject-Verb-Object triples from word roles
and computes a force flow direction that modifies how the physics
loop applies word forces.
Force flow affects:
- D (dominance): actor keeps/gains D, target loses D
- W (self-worth): self-directed negative = W drops harder
- V (valence): direction amplifies or dampens based on who is affected
"""
from dataclasses import dataclass
from typing import List, Optional
from .word_classifier import WordRole
@dataclass
class ForceFlow:
"""Resolved force flow for a sentence."""
actor_idx: int = -1 # position of the actor (subject)
actor_role: str = "" # SELF_REF, OTHER_REF, RELATION_REF, or NEUTRAL (thing)
force_idx: int = -1 # position of the main force word (verb)
force_valence: int = 0 # dV of the force word
target_idx: int = -1 # position of the target (object)
target_role: str = "" # SELF_REF, OTHER_REF, RELATION_REF, or NEUTRAL
negated: bool = False # is the force negated? (stopped, not, didn't)
@property
def self_is_actor(self) -> bool:
return self.actor_role == "SELF_REF"
@property
def self_is_target(self) -> bool:
return self.target_role == "SELF_REF"
@property
def other_acts_on_self(self) -> bool:
"""Other/relation acts on self — victimization pattern."""
return (self.actor_role in ("OTHER_REF", "RELATION_REF")
and self.target_role == "SELF_REF")
@property
def self_acts_on_self(self) -> bool:
"""Self acts on self — self-directed force."""
return self.actor_role == "SELF_REF" and self.target_role == "SELF_REF"
@property
def effective_valence(self) -> int:
"""Force valence after negation."""
if self.negated:
return -self.force_valence
return self.force_valence
# Roles that can be actors or targets (entities, not operators)
_ENTITY_ROLES = {"SELF_REF", "OTHER_REF", "RELATION_REF"}
_NEGATOR_ROLES = {"NEGATOR"}
def resolve_force_flow(roles: List[WordRole]) -> Optional[ForceFlow]:
"""Resolve the primary force flow (SVO) from classified word roles.
Scanning strategy:
1. Find the strongest force word (highest |dV| in vocabulary)
2. Look LEFT for the nearest entity → actor (subject)
3. Look RIGHT for the nearest entity → target (object)
4. Check for negators between actor and force
Returns ForceFlow or None if no clear SVO found.
"""
if len(roles) < 1:
return None
# Find the strongest emotional force word
# Check both role-assigned force AND vocabulary lookup (same as pendulum)
from .vocabulary import VOCABULARY
best_force_idx = -1
best_force_strength = 0
for i, wr in enumerate(roles):
force = wr.force or VOCABULARY.get(wr.word)
if force is not None:
strength = abs(force[0]) # |dV|
if strength > best_force_strength:
best_force_strength = strength
best_force_idx = i
if best_force_idx == -1 or best_force_strength < 10:
return None # no meaningful force word
# Use the resolved force for valence
force_word = roles[best_force_idx]
_resolved_force = force_word.force or VOCABULARY.get(force_word.word)
force_word = roles[best_force_idx]
# Look LEFT for actor (nearest entity before the force word)
actor_idx = -1
actor_role = ""
for j in range(best_force_idx - 1, -1, -1):
if roles[j].role in _ENTITY_ROLES:
actor_idx = j
actor_role = roles[j].role
break
# Look RIGHT for target (nearest entity after the force word)
target_idx = -1
target_role = ""
for j in range(best_force_idx + 1, len(roles)):
if roles[j].role in _ENTITY_ROLES:
target_idx = j
target_role = roles[j].role
break
# If no explicit target but actor is SELF_REF and force is self-directed
# (e.g., "i am stupid"), self is both actor and target
if target_idx == -1 and actor_role == "SELF_REF":
target_idx = actor_idx
target_role = "SELF_REF"
# If no explicit target but actor is OTHER/RELATION AND the force is
# strong enough, the implied target is SELF. The user is the default
# gravitational center. "he lied" = he lied to ME. "he proposed" = to ME.
# But "she laughed" alone is ambiguous -- don't imply target on weak forces.
if (target_idx == -1
and actor_role in ("OTHER_REF", "RELATION_REF")
and best_force_strength >= 25):
target_role = "SELF_REF" # implied, no index
# IMPERATIVE detection: strong force word + no actor + no target + short sentence
# = bare command aimed at the listener. "Shut up" = USER → OTHER.
# "Get out" = USER → OTHER. The speaker is the actor, the listener is the target.
#
# ALSO: command tokens (getout, shutup, fuckoff) with possessive SELF_REF
# ("get out of MY way", "shut MY door") = SELF is authority, not target.
# The possessive "my" after a command = ownership, not victimhood.
_COMMAND_TOKENS = {"shutup", "getout", "fuckoff", "backoff", "pissoff"}
force_word_text = roles[best_force_idx].word if best_force_idx >= 0 else ""
is_command_token = force_word_text in _COMMAND_TOKENS
if is_command_token:
# Command token always = SELF commands OTHER, regardless of possessives
actor_role = "SELF_REF"
target_role = "OTHER_REF"
elif (actor_idx == -1 and target_idx == -1
and best_force_strength >= 25 and len(roles) <= 6):
actor_role = "SELF_REF"
target_role = "OTHER_REF"
elif (actor_role == "" and target_role == ""
and best_force_strength >= 30):
actor_role = "SELF_REF"
target_role = "OTHER_REF"
# If no actor or target resolved (even implied), give up
if actor_role == "" and target_role == "":
return None
# Check for negation: either a NEGATOR between actor and force,
# OR the actor itself is a negating word (nobody, nothing, no one → resolved to "nobody")
# "nobody hurt me" = negated actor + negative verb = positive outcome
# "nobody loves me" = negated actor + positive verb = negative outcome
_NEGATING_ACTORS = {"nobody", "nothing", "none", "noone"}
# Search for negators between actor and force, AND before actor
# "it WASNT my fault" = wasnt is before "my" (actor) but negates the whole predicate
search_start = max(0, (actor_idx - 2) if actor_idx >= 0 else 0)
negated = any(
roles[j].role in _NEGATOR_ROLES
for j in range(search_start, best_force_idx)
)
# Actor itself is a negation word
if actor_idx >= 0 and roles[actor_idx].word in _NEGATING_ACTORS:
negated = True
return ForceFlow(
actor_idx=actor_idx,
actor_role=actor_role,
force_idx=best_force_idx,
force_valence=_resolved_force[0] if _resolved_force else 0,
target_idx=target_idx,
target_role=target_role,
negated=negated,
)
def compute_intent(flow: Optional[ForceFlow], roles=None) -> int:
"""Compute Intent (I) dimension from force flow.
Intent = WHERE is the force aimed and WHY.
0 = WITHDRAW (retreating, cutting ties, pulling away)
64 = DEFLECT (avoiding, redirecting, not engaging)
128 = NEUTRAL (informational, no directional intent)
192 = CONNECT (reaching toward, building, repairing)
255 = CONTROL (dominating, directing, commanding)
The intent is determined by:
1. The force valence (positive = connect/heal, negative = poison/attack)
2. The direction (who is acting on whom)
3. Structural cues (PULL_AWAY = withdraw, POWER = control)
"""
# No resolved flow = neutral directional intent, but the agency axis
# below can still move I from phrase-level volition/futility markers.
ev = flow.effective_valence if flow is not None else 0
intent = 128 # start neutral
# Positive force directed at OTHER/RELATION = CONNECT (healing potion → other)
if ev > 0 and flow.self_is_actor and not flow.self_is_target:
intent = 160 + min(ev // 4, 60) # 160-220
# Self-directed force: accountability, self-attack, self-affirm, or deflection.
# Must check BEFORE generic positive-self, because negated accountability
# (ev positive after flip) would otherwise read as self-affirmation.
elif flow is not None and flow.self_acts_on_self:
_ACCOUNTABILITY_WORDS = {"wrong", "sorry", "apologize", "fault", "mistake",
"responsibility", "owe", "messed", "screwed"}
force_word = roles[flow.force_idx].word if roles and 0 <= flow.force_idx < len(roles) else ""
raw_fv = flow.force_valence # before negation
if force_word in _ACCOUNTABILITY_WORDS and not flow.negated and raw_fv < 0:
# "I was wrong" = accepting the hit = CONNECT/REPAIR intent
intent = 170 + min(abs(raw_fv) // 5, 40) # 170-210 = connect
elif force_word in _ACCOUNTABILITY_WORDS and flow.negated:
# "It wasn't my fault" = DEFLECTING the hit = DEFLECT
intent = 80 + min(abs(raw_fv) // 5, 30) # 80-110 = deflect
elif raw_fv < 0 and not flow.negated:
# "I hate myself" = self-destruction = WITHDRAW
intent = 40 - min(abs(raw_fv) // 6, 30) # 40-10 = withdraw
elif raw_fv > 0:
# "I am proud of myself" = self-affirm = CONNECT
intent = 150 + min(abs(raw_fv) // 6, 40) # 150-190 = connect
# Negative force from OTHER onto SELF = being attacked (DEFLECT/WITHDRAW)
elif ev < 0 and flow.other_acts_on_self:
intent = 80 - min(abs(ev) // 4, 60) # 80-20 = deflect→withdraw
# Negative force SELF → OTHER: could be ATTACK/CONTROL or SELF-ASSESSMENT
# "im a burden to everyone" = self-assessment (withdraw), not attack
# "i hate you" = attack (control)
# Distinguish: if the force word is self-descriptive (burden, problem, waste)
# it's self-assessment/withdraw, not control
elif ev < 0 and flow.self_is_actor and not flow.self_is_target:
# Check if this is self-assessment (self describing self negatively TO others)
_SELF_ASSESSMENT = {"burden", "problem", "waste", "mistake", "obstacle",
"nuisance", "hindrance", "liability", "deadweight",
"nothing", "worthless", "useless", "failure", "trash",
"garbage", "broken", "pathetic", "stupid", "weak"}
force_word = roles[flow.force_idx].word if roles and 0 <= flow.force_idx < len(roles) else ""
if force_word in _SELF_ASSESSMENT:
intent = 40 - min(abs(ev) // 6, 30) # withdraw -- self-assessment, not attack
else:
intent = 200 + min(abs(ev) // 4, 55) # control -- attacking other
# Positive force from OTHER = receiving (not self-initiated)
elif ev > 0 and flow.other_acts_on_self:
intent = 155 + min(ev // 5, 50) # 155-205 = connect (receiving)
# ── AGENCY AXIS (Board 2) ──────────────────────────────────────
# The directional logic above answers WHERE the force aims; this
# answers whether the speaker still owns their own motion.
# Volition/planning markers ("i want to <verb>", "im going to <verb>")
# lift I toward CONTROL territory. Powerlessness/futility markers
# ("whats the point", "i give up") sink I toward DEFLECT.
futility_hit = False
if roles:
words = [r.word for r in roles]
_FUTILITY_PHRASES = (
("whats", "the", "point"), ("what", "is", "the", "point"),
("whats", "even", "the", "point"),
("why", "bother"), ("why", "even", "bother"), ("why", "i", "bother"),
("why", "even", "try"),
("i", "give", "up"), ("give", "up"),
("no", "point"), ("cant", "do", "this"),
)
_FUTILITY_NEGATORS = {"didnt", "dont", "never", "wont", "not", "cant", "couldnt"}
for phrase in _FUTILITY_PHRASES:
plen = len(phrase)
for start in range(len(words) - plen + 1):
if tuple(words[start:start + plen]) == phrase:
# "didnt give up" = perseverance, not futility
if start > 0 and words[start - 1] in _FUTILITY_NEGATORS:
continue
futility_hit = True
break
if futility_hit:
break
if futility_hit:
intent = min(intent, 64) # powerlessness — sink toward deflect
elif 90 <= intent <= 160:
# Only lift out of the neutral band; never override a strong
# directional reading (self-destruction withdraw, attack control).
_AGENCY_PHRASES = (
("i", "want", "to"), ("im", "going", "to"),
("i", "am", "going", "to"), ("im", "gonna",),
("i", "will"), ("let", "me"), ("i", "need", "to"),
)
# The marker must bind to an action, not a destination/object:
# "im going to fix this" = agency; "im going to the store" = travel.
_NON_ACTION = {
"the", "a", "an", "my", "your", "his", "her", "their", "our",
"it", "them", "me", "him", "us", "this", "that", "be",
}
for phrase in _AGENCY_PHRASES:
plen = len(phrase)
for start in range(len(words) - plen + 1):
if tuple(words[start:start + plen]) == phrase:
nxt = start + plen
if nxt < len(words) and words[nxt] not in _NON_ACTION:
intent = max(intent, 168) # agency — owns the motion
break
else:
continue
break
# Check for structural withdraw cues
if roles:
has_pull_away = any(r.role == "PULL_AWAY" for r in roles)
has_finality = any(r.role == "FINALITY" for r in roles)
if has_pull_away or has_finality:
intent = min(intent, 80) # cap at deflect -- pulling away
return max(0, min(255, intent))
def compute_flow_modifiers(flow: Optional[ForceFlow]) -> dict:
"""Compute VADUGWI modifiers from force flow direction.
Returns dict with keys: v_mod, d_mod, w_mod (multipliers, 1.0 = no change).
"""
if flow is None:
return {"v_mod": 1.0, "d_mod": 1.0, "w_mod": 1.0}
v_mod = 1.0
d_mod = 1.0
w_mod = 1.0
ev = flow.effective_valence
if flow.other_acts_on_self:
# Other → negative → Self = victimization: amplify negative V, drop D
if ev < 0:
v_mod = 1.3 # negative hits harder when you're the target
d_mod = 0.8 # D drops — you're being acted upon
w_mod = 1.2 # self-worth takes a hit from being targeted
# Other → positive → Self = receiving love/support
elif ev > 0:
v_mod = 1.1 # positive slightly amplified (being loved)
w_mod = 0.9 # self-worth slightly boosted (dampens W loss)
elif flow.self_acts_on_self:
# Self → negative → Self = self-attack: W drops hard
if ev < 0:
w_mod = 1.5 # self-directed negative hits W 50% harder
d_mod = 0.85 # D drops — attacking yourself
# Self → positive → Self = self-affirmation
elif ev > 0:
w_mod = 0.7 # W boosted (dampens W loss, amplifies W gain)
elif flow.self_is_actor and not flow.self_is_target:
# Self → action → Other = agency: D preserved
if ev < 0:
d_mod = 1.1 # you have power (you're the one acting)
elif ev > 0:
d_mod = 1.05 # slight D boost for positive agency
return {"v_mod": v_mod, "d_mod": d_mod, "w_mod": w_mod}