a. Temporary variables created in a Child's Private Scope (like that of the associated hook of macros like (for:) and (live:)) are generally not available within it's Parent's Scope (like that of a Passage). This your use-case this means that the _i temporary variable created in the (for:) macro's associated hook isn't available in the Passage.
b. Temporary variables created in one Private Scope (like the associated hook of your (for:) macro) are generally not available within a different Private Scope (like the associated hook of a (live:) macro)
c. Each iteration of a macro like (for:) and (live:) is processing it's own instance of the associated hook, and each of those instances is accessing its own copy of any temporary variables that are determined to be within Scope. This is why a temporary variable created within the Parent Scope appears to be reset to it's original value for each associated hook instance's Child Scope, because the changes to that temporary variable within that Child Scope was applied to the associated hook instances own copy of that variable and not the variable owned by the Parent Scope.
(set: _temp to 1)\
(set: $var to 1)\
(for: each _i, ...(range: 1, 9))[
(set: _temp to it + 10)\
(set: $var to it + 10)\
i: _i temp: _temp var: $var
d. While Harowe 2.x has been enhanced to tolerate an invalid white-space character or line-break between a macro and the open square bracket of it's associated hook I would suggest not relying on that.
I believe the following solution using a (live:) macro achieves the result you are looking for.
(set: $undeadDH to 0, $undeadH to false, $undeadDR to (a:))
(set: $hero to (dm: "LP",20,"CLP",20,"hit",6,"damage",0,"armour",0,"status",2))
(set: $enemy to (dm: "LP",6,"CLP",6,"hit",6,"damage",0,"armour",9,"status",2))
(for: each _i, 1,2,3,4,5,6,7,8,9)[
(if: $enemy's CLP >= _i)[
(set: $EDR to (random: 2,12))
(set: $undeadDR to it + (a: $EDR))
(if: $EDR >= $enemy's hit)[
(set: $EDH to ($EDR - $enemy's hit) + ($enemy's damage - $hero's armour))
(if: $EDH < 0)[
(set: $EDH to 0)
(set: $undeadDH to it + $EDH, $undeadH to true)
(set: $effect to (text-style: "bold") + (transition: "shudder") + (transition-time: 1s))
(set: $counter to 0)
(set: $counter to it + 1)
(if: $enemy's CLP >= $counter)[
(append: ?output)[(print: " $effect[" + (text: $undeadDR's ($counter)) + "]")]
(if: $counter is 9)[
... a couple of points about the above:
a. I needed to use a named hook (output) as the target of the (live:) macro's output because by default each iteration of that that macro automatically overwrites the output of any previous iteration, as shown by the following example.
(set: $counter to 0)\
(set: $counter to it + 1)\
(if: $counter is 9)[(stop:)]\
b. I generally use a workarea named hook along with following CSS in my story's Story Stylesheet area to hide the visual output of any large block of logic code I embed within a Passage, this saves on having to use as many collapsing white-space markup or line-break escaping markup within those blocks. You can have multiple of these workarea's within the same Passage.