From 2300d39c25207f72e6df72b14c7e6a01c6b23464 Mon Sep 17 00:00:00 2001 From: Mahtra <93822896+MahtraDR@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:07:45 +1300 Subject: [PATCH 1/2] fix(plantheal): retry hug after EV recast instead of exiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the hug returns "appreciates the sentiment" (plant has no wounds) or "no empathic bond" (bond broken), the script correctly releases and recasts EV, but then immediately exits with "Stopped before hugging" instead of retrying with the fresh plant. This fix changes the behavior to retry the hug (up to 3 times) after recasting EV, which allows the script to continue training empathy with the fresh plant that now has wounds to transfer. Changes: - Add MAX_HUG_RETRIES constant (default 3) for recursion guard - Add retries parameter to hug_plant_once() method - After "appreciates the sentiment" → recast EV → retry hug - After "no empathic bond" → recast EV → retry hug - Add max retries check to prevent infinite loops Co-Authored-By: Claude Opus 4.5 --- plantheal.lic | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plantheal.lic b/plantheal.lic index c092d330d3..cb98030e76 100644 --- a/plantheal.lic +++ b/plantheal.lic @@ -568,9 +568,17 @@ class PlantHeal m && m[1].downcase end + MAX_HUG_RETRIES = 3 + # Hug the plant once. Returns HugResult with count (0 or 1) and reason. # Reasons: :ok (hugged), :no_plant, :fully_healed, :stopped_early - def hug_plant_once + # retries: internal counter for EV recast retries (prevents infinite recursion) + def hug_plant_once(retries = MAX_HUG_RETRIES) + if retries <= 0 + DRC.message("**Max hug retries reached** — stopping to prevent infinite loop.") + return HugResult.new(0, :stopped_early) + end + noun = plant_noun_in_room unless noun @@ -609,11 +617,13 @@ class PlantHeal when /you have no empathic bond/i DRC.message("Lost empathic bond — releasing and recasting EV.") release_and_recast_ev - HugResult.new(0, :stopped_early) + # Fresh EV should restore the bond - retry the hug + return hug_plant_once(retries - 1) when HUG_APPRECIATES DRC.message("Hug returned 'appreciates the sentiment' — **releasing and recasting EV**.") release_and_recast_ev - HugResult.new(0, :stopped_early) + # Fresh plant should have wounds now - retry the hug + return hug_plant_once(retries - 1) when /Hug what\?/i # Fallback: noun was stale despite pre_hug_check (rare race condition). recast_ev_if_needed From 62fe4b0d48dbc3a787f679579d80f9159affd103 Mon Sep 17 00:00:00 2001 From: Mahtra <93822896+MahtraDR@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:09:17 +1300 Subject: [PATCH 2/2] test(plantheal): add specs for MAX_HUG_RETRIES and retry behavior - Add spec for MAX_HUG_RETRIES constant (value 3) - Add spec for retry exhaustion returning stopped_early - Add spec for retry after HUG_APPRECIATES response - Add spec for retry after "no empathic bond" response - Add spec verifying retry counter decrements on each retry Co-Authored-By: Claude Opus 4.5 --- spec/plantheal_spec.rb | 125 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/spec/plantheal_spec.rb b/spec/plantheal_spec.rb index bd99d84c92..dccbdb03ed 100644 --- a/spec/plantheal_spec.rb +++ b/spec/plantheal_spec.rb @@ -557,6 +557,10 @@ def build_instance(**overrides) expect(PlantHeal::MAX_BACKFIRE_RETRIES).to eq(2) end + it 'defines MAX_HUG_RETRIES as 3' do + expect(PlantHeal::MAX_HUG_RETRIES).to eq(3) + end + it 'defines PLANT_NOUNS regex matching plant forms' do %w[plant thicket bush briar shrub thornbush].each do |form| expect("a vela'tohr #{form}").to match(PlantHeal::PLANT_NOUNS) @@ -613,4 +617,125 @@ def build_instance(**overrides) end end end + + # --------------------------------------------------------------------------- + # hug_plant_once retry behavior + # --------------------------------------------------------------------------- + + describe '#hug_plant_once' do + # Mock DRRoom for plant_noun_in_room + before(:each) do + stub_const('DRRoom', Class.new do + def self.room_objs + $mock_room_objs || [] + end + end) + end + + it 'returns stopped_early when retries exhausted' do + instance = build_instance( + total_hugs: 0, + hug_count: 3, + threshold: 24, + heal_past_ml: false + ) + $mock_room_objs = ["an ethereal vela'tohr thicket"] + expect(DRC).to receive(:message).with(/Max hug retries reached/) + result = instance.send(:hug_plant_once, 0) + expect(result.zero?).to be true + expect(result.reason).to eq(:stopped_early) + end + + it 'retries after HUG_APPRECIATES response' do + instance = build_instance( + total_hugs: 0, + hug_count: 3, + threshold: 24, + heal_past_ml: false, + waggle_healing: false, + manual_ev: false + ) + $mock_room_objs = ["an ethereal vela'tohr thicket"] + + # First call: appreciates, second call: Roundtime + call_count = 0 + allow(DRC).to receive(:bput) do |cmd, *_patterns| + if cmd.start_with?('hug') + call_count += 1 + call_count == 1 ? 'appreciates the sentiment' : 'Roundtime: 3 sec.' + end + end + + # Stub methods that would normally run + allow(instance).to receive(:bleeding?).and_return(false) + allow(instance).to receive(:pre_hug_check).and_return('thicket') + allow(instance).to receive(:release_and_recast_ev) + allow(DRSkill).to receive(:getxp).and_return(0) + allow(DRC).to receive(:message) + + result = instance.send(:hug_plant_once, 3) + expect(result.hugs).to eq(1) + expect(result.reason).to eq(:ok) + expect(call_count).to eq(2) + end + + it 'retries after "no empathic bond" response' do + instance = build_instance( + total_hugs: 0, + hug_count: 3, + threshold: 24, + heal_past_ml: false, + waggle_healing: false, + manual_ev: false + ) + $mock_room_objs = ["an ethereal vela'tohr thicket"] + + # First call: no bond, second call: Roundtime + call_count = 0 + allow(DRC).to receive(:bput) do |cmd, *_patterns| + if cmd.start_with?('hug') + call_count += 1 + call_count == 1 ? 'you have no empathic bond' : 'Roundtime: 3 sec.' + end + end + + allow(instance).to receive(:bleeding?).and_return(false) + allow(instance).to receive(:pre_hug_check).and_return('thicket') + allow(instance).to receive(:release_and_recast_ev) + allow(DRSkill).to receive(:getxp).and_return(0) + allow(DRC).to receive(:message) + + result = instance.send(:hug_plant_once, 3) + expect(result.hugs).to eq(1) + expect(result.reason).to eq(:ok) + expect(call_count).to eq(2) + end + + it 'decrements retry counter on each retry' do + instance = build_instance( + total_hugs: 0, + hug_count: 3, + threshold: 24, + heal_past_ml: false, + waggle_healing: false, + manual_ev: false + ) + $mock_room_objs = ["an ethereal vela'tohr thicket"] + + # Always return appreciates to force retry until exhausted + allow(DRC).to receive(:bput) do |cmd, *_patterns| + cmd.start_with?('hug') ? 'appreciates the sentiment' : nil + end + + allow(instance).to receive(:bleeding?).and_return(false) + allow(instance).to receive(:pre_hug_check).and_return('thicket') + allow(instance).to receive(:release_and_recast_ev) + allow(DRSkill).to receive(:getxp).and_return(0) + allow(DRC).to receive(:message) + + result = instance.send(:hug_plant_once, 2) + expect(result.zero?).to be true + expect(result.reason).to eq(:stopped_early) + end + end end