I have given this post quite some thoughts (for a change ;) and I
somehow believe that the complexity of your code comes from a
misconception (sorry for being blunt, but please be aware that I
express only an idea not a judgment). As Brian said correctly your
inherited approach is somehow confusing and concerns are quite
distributed over your classes. So far so good. But does this
necessarily mean that we have a case of composition here? Well I do
not believe this is the case. You are not composing your behavior you
are accumulating it.(1) In other words why not just expressing what
you really want to do first, like e.g.

class Person
   def fight
    @abilities.each do | ability |
      invoke ability
    end

    def add_ability an_ability
     @abilities << an_ability
    end

   def invoke an_ability
     # I'll come to this later
   end
 end

Now we have complete control over how we combine fighting according to
our abilities, it seems an unrealistic simplification to apply all
your abilities in a fixed order for each fight anyway, thus I invite
you to imagine different implementations for @abilities.
As you can see we have decided about our "behavior" without any impact
on coupling which is hidden in #invoke.

E.g.

def invoke an_ability
   instance_eval(&an_ability)
or
  an_ability.new(self).use
or
  ....
end

and should be adapted to your needs without any thoughts on how we
"accumulated" the behavior.

I have of course only presented the upsides of my approach and am more
than happy to read about the downsides.

HTH
Robert
(1) A paradigm supporting my view would be to use traits to compose
your person, union of traits does not support a method fight in each
trait, but I am digressing.
-- 
The best way to predict the future is to invent it.
-- Alan Kay