OK, sorry, my mistake. It was not my intention that the black Cannon would ever be protected. So imagine the Elephant on e7 is not there at all. From your second posting I understand that for the 1.Hd4, where the Horse chases the Cannon and at the same time the Cannon offers exchange, you would not judge the exchange offer a valid 'excuse' for the Horse's chase, although it does excuse the Cannon's chase. (I think HaQiKi D would judge likewise.) This was exactly what my question was about.
You mention that this is is an ambiguity. Was the choice to make the chase prevail over the offer to exchange in your program an arbitrary choice, or is there an indication that this indeed must be the true rule? Perhaps the following argument can be made that the possibility to exchange should not be considered as good as being protected (and that the Horse is thus indeed chasing here): there could also exist a
static possibility to exchange
[d]r2ko1e1O/9/4e4/9/4P1N2/9/8P/9/4F4/3FK3O w
1.Hf7 Cf9 2.Hh8 Ce9 3.Hf7 ...
Here black can at any time exchange Cannons (but obviously would not want to, as this loses him a Rook). Nowhere the rules ever mention that such a static possibility to exchange would count as protection, and thus allow the new attack by the Horse. So I think everyone would agree that this is a forbidden chase of a Cannon by a Horse. (The King is a false protector when the Cannon is on e9 because there is a second attacker, making it illegal for the King to recapture.) If a static possibility to exchange is no excuse for an active chase by another piece, there seems little reason to consider a newly offered exchange such an excuse.
Now that you mention implementation:
My reason for looking at this problem again is that I am trying to fix the Xiangqi implementation in
Jocly. I was wondering how I could do this without consuming an unacceptably large amount of CPU time. My thinking also converged on a 2-stage process: first determine which pieces receive new pseudo-legal attacks on every move of the loop, in the hope that this would leave only very few pieces on which more complex testing (for legality, protection and exchange) has to be done in stage 2. The new attacks can be selectively generated (rather than just running the existing move generator for the side not to move). This is still pretty complex, though: new attacks can be 'direct' (by the moving piece from its new location), 'discovered' (R, C, H or E over the from-square), 'activated' (C over the to-square), and 'unpinning' (the moved piece lands on pin ray). The latter case is relatively rare, and can be cheaply ruled out by testing whether the to-square is orthogonally aligned with the friendly King (14 of the 90 squares, as it must also not be on an edge). But if that happens to be the case a lot more work has to be done, and in case of a Cannon pin it could even revive two additional pieces. The testing for discovered attacks can also start with testing for orthogonal alignment of the R, C, H in the piece list (the H only on adjacent squares); discovering E has only to be tested for on 8 squares of the board, in which case the Elephant can only be in two locations diagonally adjacent to that. At any move of the loop it can be tested how many of the pieces that are still chase candidates receive new attacks (and those that dont cease to be chase candidates). If this number ever drops to zero, stage 1 can be aborted, and stage 2 skipped.
In stage 2 all remaining chase candidates would then have to be checked on every move for
1) the possibility to capture their chaser, (i.e. equal type), and whether this is legal
2) whether they are protected against any of the new attacks, and whether those recaptures are legal
3) whether the attacks on them were legal in the first place
For C/H on R step 2 can be skipped. I thought it would be best to do the tests in that order; (3) is relatively cheap, but it has only a low chance to decide the matter, (most pseudo-legal moves will also be legal), so the other tests would have to be done anyway. It is very cheap to determine if (1) can aply (just comparing the piece types of attacker and chase candidate), and if it can, it usually will (again because only a minority of moves is illegal, although you also have to test for Horse blocking here). So I put that first (and then hope there was no second attacker, like in the diagram of the initial posting). Doing (2) is in general quite time consuming, but true protection will be pretty common. I am not sure whether it would be better to do (3) before (2); even if it only rarely decides the matter, it would save a lot of work in (2) when it occasionally does.
P.S.: sorry about the FEN. The current JavaScript diagram generator in this forum uses a 1-letter code for the pieces, and A and C were already in use for the Archbishop and Chancellor of Capablanca Chess. So I used the 1-letter code that was used by (very) old WinBoard versions, with O for cannOn, and F for Ferz (which moves the same as the Xiangqi Advisor). Quite inconvenient, actually. Especially since XBoard (from which the piece images are taken) now supports 66 piece types, and the alphabet has only 26 letters. So 1-letter coding is not viable anyway. It is still on my to-do list to fix that. I have another FEN generator (doing direct SVG rendering) on my own website, which allows 'dressed letters' (= letter + punctuation sign, such as X', X`, X~ and X!) as piece ID. The problem is not only to get enough different IDs, but also to assign those in such a way that they can be easily remembered. So in that other generator I used X~ to indicate 'knighted' pieces (because ~ looks somewhat like N), so that B~ and R~ can be used for Archbishop and Chancellor, and the A and C become available for other pieces. Likewise X' and X` would indicate 'wazired' and 'ferzed' pieces. Other contenders for A and C are the Alibaba and the Camel, though. Perhaps I should make the meaning of the IDs board-size dependent, and interpret A and C as Adviser and Cannon on 9x10 boards. (I already adapt the square shading in that case...)