Short version:
RESOLVE_TRIGGER_anything_but_MANDATORY really
doesn't work for TRIGGER_UPKEEP, except during the triggering card's controller's own upkeep phase. The reason this card works for the AI is because RESOLVE_TRIGGER_AI(player) is identical to RESOLVE_TRIGGER_MANDATORY when player == AI or has duh_mode() on.
Long version:
upkeep_phase(), currently living in functions/engine.c, calls dispatch_trigger(player, TRIGGER_UPKEEP, [other parameters that aren't relevant here]). "player" in upkeep_phase() is the player whose upkeep phase it is; it's always set to current_turn, since WOTC hasn't ever printed a card that lets you have phases during the other player's turn even in un-sets. The first parameter to dispatch_trigger() is who gets to decide which order triggers happen in and, for optional ones, whether they happen at all; it gets exposed to client code in the reason_for_trigger_controller global.
If you compare upkeep_trigger_ability_mode() to anything checking just about any other trigger besides TRIGGER_UPKEEP, you'll see that reason_for_trigger_controller is always checked (and almost always in the form "reason_for_trigger_controller == player", because triggers that are optional for anyone except the controller of the triggering card are quite rare). They can do this because every single other trigger in Manalink that
- actually represents an in-game trigger, not a replacement effect or other prompt that happens to be implemented with the same system as triggers (so not TRIGGER_REPLACE_CARD_DRAW, TRIGGER_PAY_TO_BLOCK, TRIGGER_MUST_BLOCK, or TRIGGER_MUST_ATTACK) and
- isn't TRIGGER_DRAW_PHASE (which is broken in exactly the same way as TRIGGER_UPKEEP)
calls dispatch_trigger2() instead, or its near-equivalent in Magic.exe at 0x4371a0.
There are two differences between dispatch_trigger() and dispatch_trigger2(). The first is that dispatch_trigger2() stores the old values of trigger_cause_controller and trigger_cause and sets them to two of its parameters; dispatch_trigger() leaves them untouched, so that either the trigger never explicitly sets them at all, or it requires its caller to do that bit of housekeeping before calling the function. (The exe function at 0x4371a0 works like dispatch_trigger(), not like dispatch_trigger2(), in this regard.) The second is that dispatch_trigger2() dispatches the trigger twice, setting reason_for_trigger_controller to a different player each time.
So the "easy" fix - and, really, the only correct one - is to change the call in functions/engine.c:upkeep_phase() to call dispatch_trigger2() instead. The reason "easy" is in scare quotes is because you've then got to go to
every single card that looks for TRIGGER_UPKEEP and make sure that it checks reason_for_trigger_controller properly, because otherwise it'll trigger twice each upkeep. Most in Manalink probably go through upkeep_trigger_ability_mode() or an equivalent, but you've got to check all the ones that eventually end up in their old exe implementations as well. On the one hand, I don't think any actual cards do. On the other, some effect cards do. On the other (where'd I get that third hand?), both of the effect cards that listen for TRIGGER_UPKEEP - PoltergeistFX, originally for
Xenic Poltergeist, and Netherlink, originally for
Nether Shadow - already check that both reason_for_trigger_controller and current_turn == player, and I think they're both completely unused besides (though that's difficult to verify for effect cards).