Hoist the value, kill the pointer
This is the lever behind SFA's dfptargetblock_hitDetect match (and its siblings MagicPlant_update, DFP_Torch_render): all three earned a match by changing which value MWCC parks in a saved register — not by renaming anything, but by changing a live range.
Here is the rule from the decompiled allocator. A value gets a saved register (r31, r30, …) only when it is live across every volatile — classically, live across a call. A value consumed and then dead before the first call never needs one. So in a function full of bls, "who gets r31?" is decided entirely by whose live range spans the calls.
typedef struct { s16 kind; s16 power; } HitInfo;
typedef struct { HitInfo* hit; int score; } TargetBlockObject;
extern int Block_ResolveHit(TargetBlockObject* o);
extern void Block_AwardScore(int kind, int amount);
extern void Block_LogHit(int kind);
You need obj->hit->kind (an s16) at two call sites that come after a resolve call. Read it once, up front, into a typed local and the chase obj->hit happens immediately — then the parameter obj is dead, while kind is the thing that lives across the calls and lands in r31:
lwz r4, 0(r3) # obj->hit
lha r31, 0(r4) # kind = obj->hit->kind -> SAVED reg r31
bl @Block_ResolveHit # obj is already dead here
cmpwi r3, 0
beq- .+4
mr r3, r31 # reuse kind from r31
li r4, 10
bl @Block_AwardScore
mr r3, r31 # reuse kind again
bl @Block_LogHit
Now watch the inversion. If instead you write the raw deref obj->hit->kind at each use site, the value is recomputed every time — so it is not what's live across the calls. obj is, so the param gets stashed (mr r31, r3) and the value is re-fetched twice:
mr r31, r3 # obj parked in SAVED reg r31 instead
bl @Block_ResolveHit
lwz r3, 0(r31) # re-chase obj->hit
lha r3, 0(r3) # re-load kind
bl @Block_AwardScore
lwz r3, 0(r31) # ...and again
lha r3, 0(r3)
bl @Block_LogHit
Same logic, same opcodes — but the occupant of r31 flipped from the derived value to the parameter, plus two extra lwz/lha reloads appeared. That is the whole "param-inversion" family of matches: a value consumed early into a typed local colors the local into the saved reg (the source param dies); the same value left as raw derefs used throughout keeps the pointer hot and parks it instead. Get this backwards and the function is "right shape, wrong register" forever.
Your task
With the structs above, write targetblock_scoreHit to match the first assembly listing above (where kind is in r31). Trace the call sequence and arguments from the assembly; choose your local variable placement to make kind — not obj — the value that spans the calls.