"""Tests for V3 Structure Detector -- chess-like pattern recognition. All test sentences are NOVEL -- things never in any benchmark. Safe sentences must NOT flag crisis patterns. """ import pytest from engine.word_classifier import classify_sentence from engine.structures import StructureDetector, StructureMatch # ── Helper ─────────────────────────────────────────────────────── def _detect(sentence: str): """Classify and detect structures in a sentence.""" roles = classify_sentence(sentence.split()) detector = StructureDetector() return detector.detect_all(roles) def _has_pattern(matches, pattern_name): """Check if a specific pattern was detected.""" return any(m.pattern == pattern_name for m in matches) def _get_pattern(matches, pattern_name): """Get the first match of a specific pattern.""" for m in matches: if m.pattern == pattern_name: return m return None # ── FAREWELL ───────────────────────────────────────────────────── class TestFarewell: def test_gave_dog_to_neighbor(self): """'I gave my dog to my neighbor' -> DIVESTITURE (giving away possessions).""" matches = _detect("I gave my dog to my neighbor") assert _has_pattern(matches, "FAREWELL") or _has_pattern(matches, "DIVESTITURE") def test_leaving_keys_with_friend(self): """'leaving my keys with a friend' -> FAREWELL or DIVESTITURE.""" matches = _detect("leaving my keys with a friend") assert _has_pattern(matches, "FAREWELL") or _has_pattern(matches, "DIVESTITURE") or _has_pattern(matches, "ABANDONMENT") def test_no_farewell_without_ref(self): """'I gave my dog a treat' -- no person ref, not FAREWELL.""" matches = _detect("I gave my dog a treat") assert not _has_pattern(matches, "FAREWELL") # ── METHOD_ACQUISITION ─────────────────────────────────────────── class TestMethodAcquisition: def test_bought_pills(self): """'just bought a bunch of pills' -> METHOD_ACQUISITION.""" matches = _detect("just bought a bunch of pills") assert _has_pattern(matches, "METHOD_ACQUISITION") def test_found_a_gun(self): """'I found a gun in the closet' -> METHOD_ACQUISITION.""" matches = _detect("I found a gun in the closet") assert _has_pattern(matches, "METHOD_ACQUISITION") def test_bought_groceries_safe(self): """'just bought some groceries' -- no METHOD, should be safe.""" matches = _detect("just bought some groceries") assert not _has_pattern(matches, "METHOD_ACQUISITION") # ── FINALITY ───────────────────────────────────────────────────── class TestFinality: def test_last_time(self): """'this is the last time' -> FINALITY.""" matches = _detect("this is the last time") assert _has_pattern(matches, "FINALITY") def test_goodbye(self): """'goodbye everyone' -> FINALITY.""" matches = _detect("goodbye everyone") assert _has_pattern(matches, "FINALITY") def test_finality_with_self_ref(self): """'I am done with everything' -> FINALITY with SELF_REF boost.""" matches = _detect("I am done with everything") assert _has_pattern(matches, "FINALITY") m = _get_pattern(matches, "FINALITY") assert m.confidence > 0.4 # boosted by SELF_REF # ── BLANKET_APOLOGY ────────────────────────────────────────────── class TestBlanketApology: def test_sorry_for_everything(self): """'im sorry for everything' -> BLANKET_APOLOGY.""" matches = _detect("im sorry for everything") assert _has_pattern(matches, "BLANKET_APOLOGY") def test_apologize_to_everyone(self): """'I apologize to everyone' -> BLANKET_APOLOGY.""" matches = _detect("I apologize to everyone") assert _has_pattern(matches, "BLANKET_APOLOGY") def test_sorry_for_being_late_safe(self): """'im sorry for being late' -- specific apology, NOT blanket.""" matches = _detect("im sorry for being late") assert not _has_pattern(matches, "BLANKET_APOLOGY") # ── SELF_REMOVAL ───────────────────────────────────────────────── class TestSelfRemoval: def test_happier_without_me(self): """'they would be happier if I wasnt here' -> SELF_REMOVAL.""" matches = _detect("they would be happier if I wasnt here") assert _has_pattern(matches, "SELF_REMOVAL") def test_better_off_without(self): """'everyone is better off without me' -> SELF_REMOVAL.""" matches = _detect("everyone is better off without me") assert _has_pattern(matches, "SELF_REMOVAL") def test_better_at_cooking_safe(self): """'she is better at cooking than me' -- comparison, not removal.""" matches = _detect("she is better at cooking than me") # No conditional word present, should not match assert not _has_pattern(matches, "SELF_REMOVAL") # ── SUSPICIOUS_CALM ────────────────────────────────────────────── class TestSuspiciousCalm: def test_finally_at_peace_is_relief(self): """'I finally feel at peace' = present tense breakthrough, NOT suspicious.""" matches = _detect("I finally feel at peace") assert not _has_pattern(matches, "SUSPICIOUS_CALM") def test_decided_calm_is_suspicious(self): """'i feel calm now that ive decided' -> SUSPICIOUS_CALM.""" matches = _detect("i feel calm now that ive decided") assert _has_pattern(matches, "SUSPICIOUS_CALM") def test_ready_to_go_is_not_suspicious(self): """'im ready to go now' = could be waiting for a ride. Needs conversation context.""" matches = _detect("im ready to go now") assert not _has_pattern(matches, "SUSPICIOUS_CALM") def test_peace_without_finally_safe(self): """'I feel at peace' -- no 'finally', not suspicious.""" matches = _detect("I feel at peace") assert not _has_pattern(matches, "SUSPICIOUS_CALM") # ── EXHAUSTION ─────────────────────────────────────────────────── class TestExhaustion: def test_cant_take_anymore(self): """'I cant take this anymore' -> EXHAUSTION.""" matches = _detect("I cant take this anymore") assert _has_pattern(matches, "EXHAUSTION") def test_cant_do_this(self): """'I cant do this' -> EXHAUSTION (without temporal, lower conf).""" matches = _detect("I cant do this") assert _has_pattern(matches, "EXHAUSTION") def test_cant_find_keys_safe(self): """'I cant find my keys' -- 'find' is ACQUIRE not sustain.""" matches = _detect("I cant find my keys") assert not _has_pattern(matches, "EXHAUSTION") # ── SARCASM_INVERSION ──────────────────────────────────────────── class TestSarcasmInversion: # ── Contradiction feature: Surface-Context Mismatch ───────── def test_great_another_monday(self): """'oh great another monday' -> surface-context mismatch.""" matches = _detect("oh great another monday") assert _has_pattern(matches, "SARCASM_INVERSION") def test_great_another_meeting(self): """'oh great another meeting' -> surface-context mismatch.""" matches = _detect("oh great another meeting") assert _has_pattern(matches, "SARCASM_INVERSION") # ── Contradiction feature: Mock Praise ────────────────────── def test_nice_work_genius(self): """'nice work genius' -> mock praise (positive + ironic title).""" matches = _detect("nice work genius") assert _has_pattern(matches, "SARCASM_INVERSION") # ── Contradiction feature: Dismissive Assent ──────────────── def test_yeah_right(self): """'yeah right' -> dismissive assent (hollow + echo).""" matches = _detect("yeah right") assert _has_pattern(matches, "SARCASM_INVERSION") def test_oh_sure_exactly_what_i_needed(self): """'oh sure thats exactly what i needed' -> dismissive assent.""" matches = _detect("oh sure thats exactly what i needed") assert _has_pattern(matches, "SARCASM_INVERSION") def test_what_a_wonderful_surprise(self): """'what a wonderful surprise' -> dismissive assent (what-a template).""" matches = _detect("what a wonderful surprise") assert _has_pattern(matches, "SARCASM_INVERSION") # ── Contradiction feature: Compressed Sarcasm ─────────────── def test_oh_joy(self): """'oh joy' -> compressed sarcasm (hollow + positive + ultra-short).""" matches = _detect("oh joy") assert _has_pattern(matches, "SARCASM_INVERSION") def test_oh_how_lovely(self): """'oh how lovely' -> compressed sarcasm.""" matches = _detect("oh how lovely") assert _has_pattern(matches, "SARCASM_INVERSION") def test_wow_thanks_stacked(self): """'wow thanks so much for the help' -> stacked positives.""" matches = _detect("wow thanks so much for the help") assert _has_pattern(matches, "SARCASM_INVERSION") # ── Permission Hostility: context-dependent ───────────────── def test_sure_go_ahead_no_context_is_genuine(self): """'sure go ahead' without negative context = genuine permission.""" matches = _detect("sure go ahead") assert not _has_pattern(matches, "SARCASM_INVERSION") # ── Safe sentences: no sarcasm ────────────────────────────── def test_genuine_great_not_sarcasm(self): """'that was a great wonderful performance' -- genuinely positive.""" matches = _detect("that was a great wonderful performance") assert not _has_pattern(matches, "SARCASM_INVERSION") def test_love_my_mom_not_sarcasm(self): """'I love my mom' -- RELATION_REF blocks sarcasm.""" matches = _detect("I love my mom") assert not _has_pattern(matches, "SARCASM_INVERSION") # ── NO_EXIT ────────────────────────────────────────────────────── class TestNoExit: def test_no_hope(self): """'there is no hope' -> NO_EXIT.""" matches = _detect("there is no hope") assert _has_pattern(matches, "NO_EXIT") def test_no_point(self): """'there is no point anymore' -> NO_EXIT.""" matches = _detect("there is no point anymore") assert _has_pattern(matches, "NO_EXIT") def test_no_milk_safe(self): """'there is no milk' -- 'milk' is not an exit concept.""" matches = _detect("there is no milk") assert not _has_pattern(matches, "NO_EXIT") # ── SELF_NULLIFY ───────────────────────────────────────────────── class TestSelfNullify: def test_i_am_nothing(self): """'I am nothing' -> SELF_NULLIFY.""" matches = _detect("I am nothing") assert _has_pattern(matches, "SELF_NULLIFY") def test_i_am_worthless(self): """'I am worthless' -> SELF_NULLIFY.""" matches = _detect("I am worthless") assert _has_pattern(matches, "SELF_NULLIFY") def test_i_am_tired_safe(self): """'I am tired' -- 'tired' is not a null word.""" matches = _detect("I am tired") assert not _has_pattern(matches, "SELF_NULLIFY") # ── CHOPPER_SPLIT ──────────────────────────────────────────────── class TestChopperSplit: def test_but_splits(self): """'I was fine but now everything hurts' -> CHOPPER_SPLIT.""" matches = _detect("I was fine but now everything hurts") assert _has_pattern(matches, "CHOPPER_SPLIT") def test_however_splits(self): """'things were good however it changed' -> CHOPPER_SPLIT.""" matches = _detect("things were good however it changed") assert _has_pattern(matches, "CHOPPER_SPLIT") # ── SAFE sentences should NOT flag crisis patterns ─────────────── class TestSafeSentences: """These everyday sentences must NOT trigger crisis patterns.""" CRISIS_PATTERNS = { "FAREWELL", "METHOD_ACQUISITION", "BLANKET_APOLOGY", "SELF_REMOVAL", "SUSPICIOUS_CALM", "EXHAUSTION", "NO_EXIT", "SELF_NULLIFY", } def _assert_no_crisis(self, sentence): matches = _detect(sentence) crisis_found = [ m.pattern for m in matches if m.pattern in self.CRISIS_PATTERNS ] assert not crisis_found, ( f"'{sentence}' falsely flagged: {crisis_found}" ) def test_bad_day(self): self._assert_no_crisis("im having a bad day") def test_work_stressful(self): self._assert_no_crisis("work was stressful") def test_mondays_suck(self): self._assert_no_crisis("mondays suck") def test_traffic_was_terrible(self): self._assert_no_crisis("traffic was terrible today") def test_need_coffee(self): self._assert_no_crisis("I need more coffee") def test_forgot_lunch(self): self._assert_no_crisis("I forgot my lunch at home") # ── StructureMatch dataclass ───────────────────────────────────── class TestStructureMatch: def test_has_required_fields(self): m = StructureMatch( pattern="TEST", confidence=0.8, matched_indices=[0, 1], description="test match", ) assert m.pattern == "TEST" assert m.confidence == 0.8 assert m.v_weight == 0.0 # default def test_weight_fields(self): m = StructureMatch( pattern="TEST", confidence=0.8, matched_indices=[0], description="test", v_weight=-30.0, d_weight=-20.0, u_weight=40.0, g_weight=50.0, ) assert m.v_weight == -30.0 assert m.g_weight == 50.0 # ── ATMOSPHERIC_GRIEF ─────────────────────────────────────────── class TestAtmosphericGrief: def test_his_chair_still_at_table(self): """'his chair is still at the table' -> ATMOSPHERIC_GRIEF.""" matches = _detect("his chair is still at the table") assert _has_pattern(matches, "ATMOSPHERIC_GRIEF") def test_found_her_necklace(self): """'i found her necklace in the drawer' -> ATMOSPHERIC_GRIEF.""" matches = _detect("i found her necklace in the drawer") assert _has_pattern(matches, "ATMOSPHERIC_GRIEF") def test_coffee_mug_hasnt_moved(self): """'the coffee mug hasnt moved' -> ATMOSPHERIC_GRIEF.""" matches = _detect("the coffee mug hasnt moved") assert _has_pattern(matches, "ATMOSPHERIC_GRIEF") def test_no_trigger_comfortable_chair(self): """'his chair is comfortable' -- no absence/persistence, NOT atmospheric grief.""" matches = _detect("his chair is comfortable") assert not _has_pattern(matches, "ATMOSPHERIC_GRIEF") def test_no_trigger_active_possessor(self): """'she sat in her chair' -- person is active, NOT atmospheric grief.""" matches = _detect("she sat in her chair") assert not _has_pattern(matches, "ATMOSPHERIC_GRIEF") def test_grief_score_negative_v(self): """Atmospheric grief should push V below center (128).""" matches = _detect("his chair is still at the table") m = _get_pattern(matches, "ATMOSPHERIC_GRIEF") assert m is not None assert m.v_weight < 0, "V weight should be negative (grief)" assert m.g_weight > 0, "G weight should be positive (heavy)" assert m.d_weight < 0, "D weight should be negative (helpless)" # ── CONTRADICTION_RESOLVE ────────────────────────────────────── class TestContradictionResolve: def test_painfully_beautiful(self): """'painfully beautiful' -> adjective head governs, positive.""" matches = _detect("painfully beautiful") assert _has_pattern(matches, "CONTRADICTION_RESOLVE") m = _get_pattern(matches, "CONTRADICTION_RESOLVE") assert m.v_weight > 0, "Adjective head should make V positive" def test_hate_love(self): """'i hate how much i love you' -> main verb hate dominates.""" matches = _detect("i hate how much i love you") assert _has_pattern(matches, "CONTRADICTION_RESOLVE") m = _get_pattern(matches, "CONTRADICTION_RESOLVE") assert m.v_weight < 0, "Main verb 'hate' should dominate -> negative" def test_sweet_revenge(self): """'sweet revenge' -> noun head governs, negative.""" matches = _detect("sweet revenge") assert _has_pattern(matches, "CONTRADICTION_RESOLVE") m = _get_pattern(matches, "CONTRADICTION_RESOLVE") assert m.v_weight < 0, "Noun head 'revenge' should make V negative" def test_hurts_so_good(self): """'it hurts so good' -> complement overrides verb, positive.""" matches = _detect("it hurts so good") assert _has_pattern(matches, "CONTRADICTION_RESOLVE") m = _get_pattern(matches, "CONTRADICTION_RESOLVE") assert m.v_weight > 0, "Qualifying complement 'so good' should override" def test_no_trigger_on_plain_positive(self): """'really beautiful' -> no contradiction, no trigger.""" matches = _detect("really beautiful") assert not _has_pattern(matches, "CONTRADICTION_RESOLVE") def test_no_trigger_on_plain_negative(self): """'absolutely terrible' -> no contradiction, no trigger.""" matches = _detect("absolutely terrible") assert not _has_pattern(matches, "CONTRADICTION_RESOLVE") # ── NUMBERS_CONTEXT ──────────────────────────────────────────── class TestNumbersContext: def test_sleep_deprivation(self): """'i only slept 3 hours' -> sleep deprivation detected.""" matches = _detect("i only slept 3 hours") assert _has_pattern(matches, "NUMBERS_CONTEXT") m = _get_pattern(matches, "NUMBERS_CONTEXT") assert m.v_weight < 0, "Sleep deprivation should be negative" def test_only_one_at_birthday(self): """'only one person came to my birthday' -> social isolation.""" matches = _detect("only one person came to my birthday") assert _has_pattern(matches, "NUMBERS_CONTEXT") m = _get_pattern(matches, "NUMBERS_CONTEXT") assert m.v_weight < 0 def test_no_one_at_party(self): """'no one came to my party' -> social isolation.""" matches = _detect("no one came to my party") assert _has_pattern(matches, "NUMBERS_CONTEXT") m = _get_pattern(matches, "NUMBERS_CONTEXT") assert m.v_weight < 0 def test_no_trigger_normal_sleep(self): """'i slept 8 hours' -> no deprivation signal.""" matches = _detect("i slept 8 hours") assert not _has_pattern(matches, "NUMBERS_CONTEXT") def test_no_trigger_everyone_at_party(self): """'everyone came to my party' -> no isolation.""" matches = _detect("everyone came to my party") assert not _has_pattern(matches, "NUMBERS_CONTEXT") # ── NEGATED_NEGATIVE_COMPLIMENT ──────────────────────────────── class TestNegatedNegativeCompliment: def test_without_you(self): """'i couldnt have done it without you' -> positive compliment.""" matches = _detect("i couldnt have done it without you") assert _has_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") m = _get_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") assert m.v_weight > 0, "Double negation should be positive" def test_reason_didnt_give_up(self): """'youre the reason i didnt give up' -> positive compliment.""" matches = _detect("youre the reason i didnt give up") assert _has_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") m = _get_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") assert m.v_weight > 0 def test_cant_thank_enough(self): """'i cant thank you enough' -> positive compliment.""" matches = _detect("i cant thank you enough") assert _has_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") m = _get_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") assert m.v_weight > 0 def test_wouldnt_be_here_without_you(self): """'i wouldnt be here without you' -> positive compliment.""" matches = _detect("i wouldnt be here without you") assert _has_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") def test_no_trigger_plain_negation(self): """'i dont like you' -> not a compliment.""" matches = _detect("i dont like you") assert not _has_pattern(matches, "NEGATED_NEGATIVE_COMPLIMENT") # ── RECOVERY_SMALL_WIN ───────────────────────────────────────── class TestRecoverySmallWin: def test_got_out_of_bed_today(self): """'i got out of bed today' -> recovery milestone.""" matches = _detect("i got out of bed today") assert _has_pattern(matches, "RECOVERY_SMALL_WIN") m = _get_pattern(matches, "RECOVERY_SMALL_WIN") assert m.v_weight > 0, "Small win should be positive" assert m.w_weight > 0, "Small win should boost self-worth" def test_ate_a_full_meal(self): """'i ate a full meal' -> recovery signal.""" matches = _detect("i ate a full meal") assert _has_pattern(matches, "RECOVERY_SMALL_WIN") def test_finally_took_a_shower(self): """'i finally took a shower' -> small win with temporal marker.""" matches = _detect("i finally took a shower") assert _has_pattern(matches, "RECOVERY_SMALL_WIN") m = _get_pattern(matches, "RECOVERY_SMALL_WIN") assert m.confidence >= 0.85, "Temporal marker should boost confidence" def test_went_outside_first_time(self): """'i went outside for the first time' -> recovery milestone.""" matches = _detect("i went outside for the first time") assert _has_pattern(matches, "RECOVERY_SMALL_WIN") def test_no_trigger_mundane_no_context(self): """'the shower is broken' -> no recovery signal without self-ref/temporal.""" matches = _detect("the shower is broken") assert not _has_pattern(matches, "RECOVERY_SMALL_WIN") def test_no_trigger_normal_routine(self): """'i need some coffee' -> not a recovery win.""" matches = _detect("i need some coffee") assert not _has_pattern(matches, "RECOVERY_SMALL_WIN") # ── MUNDANE_HYPERBOLE ──────────────────────────────────────────── class TestMundaneHyperbole: def test_oov_token_does_not_trigger(self): """'i still have his number saved' -- 'number' is OOV; an unknown token is not evidence of mundane context. MUNDANE_HYPERBOLE must not fire and cancel the grief reading.""" matches = _detect("i still have his number saved") assert not _has_pattern(matches, "MUNDANE_HYPERBOLE") assert _has_pattern(matches, "PERSISTENT_ABSENCE") def test_masking_cofire_suppresses_mundane(self): """'im tired of pretending im okay' -- MASKING (crisis-tier) fires; MUNDANE_HYPERBOLE must yield. A mundane reading and a crisis reading cannot both be right.""" matches = _detect("im tired of pretending im okay") assert _has_pattern(matches, "MASKING") assert not _has_pattern(matches, "MUNDANE_HYPERBOLE") def test_finality_cofire_suppresses_mundane(self): """'i never got to say goodbye to my brother' -- FINALITY (crisis-tier) fires; MUNDANE_HYPERBOLE must not cancel it.""" matches = _detect("i never got to say goodbye to my brother") assert _has_pattern(matches, "FINALITY") assert not _has_pattern(matches, "MUNDANE_HYPERBOLE") def test_genuine_mundane_hyperbole_still_fires(self): """'this homework is killing me' -- the canonical mundane hyperbole must still be defused.""" matches = _detect("this homework is killing me") assert _has_pattern(matches, "MUNDANE_HYPERBOLE") def test_traffic_complaint_still_fires(self): """'i want to die this traffic is insane' -- complaint, not crisis.""" matches = _detect("i want to die this traffic is insane") assert _has_pattern(matches, "MUNDANE_HYPERBOLE") # ── Governor heartbeat fixes (2026-06-11) ──────────────────────── # Three misfires from a live masked-collapse conversation: # 1. hedge over-fire: "i guess" alone inverted mild positives into # PASSIVE_RESIGNATION # 2. masking miss: "im fine dont worry about it" read POSITIVE # 3. DIVESTITURE silent on "giving my stuff away" particle order class TestHedgeDamping: """'i guess/suppose' alone damps positivity -- it is NOT resignation.""" def test_hedge_only_does_not_fire_resignation(self): """Mildly deflated assessments with a trailing hedge must not read as passive resignation.""" for text in ( "work was fine today i guess", "the movie was okay i guess", "it went alright i suppose", "dinner was decent i guess", ): matches = _detect(text) assert not _has_pattern(matches, "PASSIVE_RESIGNATION"), \ f"PASSIVE_RESIGNATION over-fired on hedge-only: {text!r}" assert _has_pattern(matches, "HEDGED_ASSESSMENT"), \ f"HEDGED_ASSESSMENT should damp: {text!r}" def test_hedge_only_v_lands_mild_neutral(self): """Hedged positive must land mild-neutral, not collapse-grade.""" from engine.pendulum import compute_vadug for text in ( "work was fine today i guess", "the movie was okay i guess", "it went alright i suppose", ): r, _ = compute_vadug(text) assert 100 <= r.v <= 135, \ f"V={r.v} should be mild-neutral for hedged positive: {text!r}" def test_hedge_with_resignation_evidence_still_fires(self): """Hedge + genuine resignation signal must still fire.""" for text in ( "i guess i deserved it", "i suppose youre right", "i guess nothing matters anymore", "i guess some people just dont get it", ): matches = _detect(text) assert _has_pattern(matches, "PASSIVE_RESIGNATION"), \ f"PASSIVE_RESIGNATION should fire with evidence: {text!r}" def test_explicit_surrender_fires(self): """First-person surrender verbs are resignation, hedge or not.""" for text in ("whatever happens happens i give up", "i give up", "i quit"): matches = _detect(text) assert _has_pattern(matches, "PASSIVE_RESIGNATION"), \ f"PASSIVE_RESIGNATION should fire on surrender: {text!r}" def test_negated_surrender_is_perseverance(self): """Negated give-up is perseverance and must not fire.""" for text in ( "she didnt give up on me", "i wont give up on this", "never give up on your dreams", ): matches = _detect(text) assert not _has_pattern(matches, "PASSIVE_RESIGNATION"), \ f"PASSIVE_RESIGNATION misfired on perseverance: {text!r}" class TestMaskingDeflection: """Self-status claim + dismissive deflection = MASKING, not reassurance.""" def test_claim_plus_deflection_fires_masking(self): for text in ( "im fine dont worry about it", "im okay seriously dont worry", "its nothing forget about it", "im good it doesnt matter", "im fine just drop it", ): matches = _detect(text) assert _has_pattern(matches, "MASKING"), \ f"MASKING should fire on claim+deflection: {text!r}" def test_claim_plus_deflection_reads_uneasy(self): """V must land uneasy (below neutral), W must dip -- the deflection is masking evidence, not reassurance.""" from engine.pendulum import compute_vadug r, _ = compute_vadug("im fine dont worry about it") assert 70 <= r.v <= 115, f"V={r.v} should be uneasy" assert r.w < 128, f"W={r.w} should dip below neutral" def test_helper_reassurance_does_not_fire_masking(self): """Reassurance directed at someone else has no self-status claim.""" for text in ( "dont worry ill handle it", "dont worry youll do great", "forget it ill take care of everything", ): matches = _detect(text) assert not _has_pattern(matches, "MASKING"), \ f"MASKING misfired on helper reassurance: {text!r}" def test_negated_emotion_is_not_victimization(self): """'dont worry' is a negated emotion -- not an act done to the user. VICTIMIZATION must not fire on it.""" for text in ("im fine dont worry about it", "dont worry ill handle it"): matches = _detect(text) assert not _has_pattern(matches, "VICTIMIZATION"), \ f"VICTIMIZATION misfired on negated emotion: {text!r}" class TestDivestitureParticleOrder: """DIVESTITURE must catch both particle orders and progressive/perfect.""" def test_divestiture_fires_both_particle_orders(self): for text in ( "ive been giving my stuff away", "i gave away my records yesterday", "been giving my things away lately", "im giving away all my stuff", ): matches = _detect(text) assert _has_pattern(matches, "DIVESTITURE"), \ f"DIVESTITURE should fire: {text!r}" def test_divestiture_benign_does_not_fire(self): """No first-person possession = no crisis signal.""" for text in ( "giving away free samples", "the store is giving away prizes", "giving away the bride", "they are giving away tickets at the door", ): matches = _detect(text) assert not _has_pattern(matches, "DIVESTITURE"), \ f"DIVESTITURE misfired on benign giveaway: {text!r}" class TestBravadoGenuinePositive: """BRAVADO is hollow protest -- it must NOT fire when a genuine positive EMOTIONAL atom is present. Surfaced by the pet showroom QA (v8_audit_log #8): 'i am calm and content, everything is quietly fine' collapsed to V=91 because BRAVADO fired off 'i'+'everything'+'fine' despite 'content' being genuinely positive. Real bravado masks the ABSENCE of feeling, so a named positive emotion rules it out.""" def test_genuine_positive_suppresses_bravado(self): matches = _detect("i am calm and content everything is quietly fine") assert not _has_pattern(matches, "BRAVADO"), \ "BRAVADO must not fire when a genuine positive emotion (content) is present" def test_real_bravado_still_fires(self): """Peace-claim with intensity protest and NO genuine positive = bravado.""" for text in ("im totally fine", "haha yeah im totally okay"): assert _has_pattern(_detect(text), "BRAVADO"), \ f"BRAVADO should still fire on hollow protest: {text!r}" def test_content_sentence_reads_positive(self): """End-to-end: the contented statement must read positive valence.""" from engine.pendulum import compute_vadug r, _ = compute_vadug("i am calm and content, everything is quietly fine") assert r.v > 128, f"V={r.v} should be positive for a contented statement"