| Human-side AI goalie waits for a B request, then passes to that pad's skater.
| Requests can wait through recovery. Native hold timer/whistle and explicit manual control remain.
    .equ OBJ,0xFFFFB04A
    .equ REQ,0xFFFFF058              | transient BC64 target+1, passer, OUTL signature
    .equ ASK,0xFFFFF064              | pending physical pad offset+1 (1/3/5/7), goalie object index
    .text
    .globl _start
_start:
    bra.w decide
    bra.w select_target
    bra.w goalie_direction
    bra.w request

decide:
    movem.l %d0-%d7/%a0-%a6,-(%sp)
    clr.l REQ
    clr.l REQ+4
    tst.w 0x34(%a3)
    bne.w original
    btst #3,0x62(%a3)
    bne.w original
    tst.w 0xFFFFD046
    beq.s adapter_ready
    jsr 0x2EA610                     | adapter head-to-head retains native goalie behaviour
    bne.w original
adapter_ready:
    btst #0,0xFFFFC2FA
    bne.w original
    tst.w 0xFFFFD412
    bne.w original
    move.b 0xFFFFC2EA,%d0
    andi.b #0x11,%d0
    bne.w original
    btst #3,0xFFFFC2F6
    bne.w original
    move.w 0x52(%a3),%d0
    moveq #1,%d5
    cmpi.w #5,%d0
    beq.s side_ready
    cmpi.w #11,%d0
    bne.w original
    moveq #2,%d5
side_ready:
    lea 0xFFFFC328,%a1
    moveq #3,%d0
1:  cmp.w (%a1)+,%d5
    beq.s human_side
    dbra %d0,1b
    bra.w original                 | CPU-only keeper retains native AI
human_side:
    tst.w ASK
    beq.w wait                     | no automatic/random outlet for a human-side AI goalie
    move.w 0x52(%a3),%d0
    cmp.w ASK+2,%d0
    bne.w cancel
    btst #1,0x63(%a3)             | queue a tap until the diving/catch recovery has completed
    bne.w wait
    btst #3,0x63(%a3)
    bne.w wait
    move.w ASK,%d7
    subq.w #1,%d7
    cmpi.w #6,%d7
    bhi.w cancel
    btst #0,%d7
    bne.w cancel
    lea 0xFFFFC328,%a1
    cmp.w (%a1,%d7.w),%d5
    bne.w cancel
    lea 0xFFFFC320,%a1
    move.w (%a1,%d7.w),%d0
    cmpi.w #11,%d0
    bhi.w cancel
    moveq #0,%d1                    | DIVU consumes all 32 bits, not only the object index word
    move.w %d0,%d1
    divu.w #6,%d1
    addq.w #1,%d1
    cmp.w %d5,%d1
    bne.w cancel
    lsl.w #7,%d0
    lea OBJ,%a5
    adda.w %d0,%a5
    tst.w 0x34(%a5)
    ble.w cancel
    btst #3,0x62(%a5)
    beq.w cancel
    btst #2,0x63(%a5)
    bne.w wait
    btst #5,0x62(%a5)
    bne.w wait
    move.w 0x52(%a5),%d0
    addq.w #1,%d0
    move.w %d0,REQ
    move.w 0x52(%a3),REQ+2
    move.l #0x4F55544C,REQ+4
    move.w (%a5),%d0
    sub.w (%a3),%d0
    move.w 0x14(%a5),%d1
    sub.w 0x14(%a3),%d1
    jsr 0x10676
    move.w %d0,0xFFFFBEDC
    move.w %d0,0x54(%a3)
    jsr 0xBC64                      | native pass, exact target/vector hooks below
    clr.l REQ
    clr.l REQ+4
    move.w 0x52(%a3),%d0
    cmp.w 0xFFFFB7AA,%d0
    beq.s wait                      | if native pass cannot start yet, retain this possession's request
cancel:
    clr.l ASK
wait:
    movem.l (%sp)+,%d0-%d7/%a0-%a6
    jmp 0xD880
original:
    clr.l ASK
    movem.l (%sp)+,%d0-%d7/%a0-%a6
    tst.w 0x48(%a3)
    bpl.s positive_timer
    jmp 0xD764
positive_timer:
    jmp 0xD76A

| Called before ordinary B switching. D1=new buttons, D4=physical pad0/2/4/6,
| A3=controlled human skater. D0=1 means queue/consume B; all other registers kept.
request:
    movem.l %d1-%d7/%a0-%a6,-(%sp)
    moveq #0,%d0
    move.w 0xFFFFB7AA,%d2
    bpl.s 1f
    bsr.w incoming                     | r13: a loose puck at your goalie counts as his
1:  | Adapter reads can publish a short request in VBlank; do not race that
    | publication while replacing a previous possession's pending owner.
    move.w %sr,-(%sp)
    ori.w #0x700,%sr
    cmp.w ASK+2,%d2
    beq.s same_owner
    clr.l ASK
same_owner:
    move.w (%sp)+,%sr
    or.w %d3,%d1                    | include a held B across the catch/previous switch latch
    btst #4,%d1
    beq.w request_done
    .if WITH_IMMEDIATE_B
    move.l (%sp),%d3                   | original new-press D1 before OR with held buttons
    btst #4,%d3
    beq.s queue_original
    lea 0xFFFFF0E4,%a0
    tst.w 0xFFFFD046
    beq.s physical_y
    lea 0xFFFFF0BA,%a0
physical_y:
    tst.b (%a0,%d4.w)
    bne.s queue_original               | Y-generated logical B retains the native outlet
    moveq #2,%d0                       | ordinary new B switches immediately unless a valid keeper request
    .if WITH_CATCH_AI
    bra.w queue_original              | owning AI keeper: pass to this pad's current skater
    .else
    bra.w request_done
    .endif
queue_original:
    .endif
    btst #0,0xFFFFC2FA
    bne.w request_done
    tst.w 0xFFFFD412
    bne.w request_done
    move.b 0xFFFFC2EA,%d3
    andi.b #0x11,%d3
    bne.w request_done
    cmpi.w #6,%d4
    bhi.w request_done
    moveq #1,%d5
    cmpi.w #5,%d2
    beq.s request_side
    cmpi.w #11,%d2
    bne.w request_done
    moveq #2,%d5
request_side:
    lea 0xFFFFC328,%a1
    cmp.w (%a1,%d4.w),%d5
    bne.w request_done
    tst.w 0x34(%a3)
    ble.w request_done
    btst #3,0x62(%a3)
    beq.w request_done
    move.w 0x52(%a3),%d6
    lea 0xFFFFC320,%a1
    cmp.w (%a1,%d4.w),%d6
    bne.w request_done
    move.w %d2,%d3
    lsl.w #7,%d3
    lea OBJ,%a0
    adda.w %d3,%a0
    tst.w 0x34(%a0)
    bne.w request_done
    btst #3,0x62(%a0)
    bne.w request_done               | explicit/manual goalie keeps native controls
    move.w %sr,-(%sp)
    ori.w #0x700,%sr
    tst.w ASK
    bne.s request_published          | first valid request wins until served/cancelled
    move.w %d4,ASK
    addq.w #1,ASK
    move.w %d2,ASK+2
    .if WITH_OUTLET_DEADLINE
    jsr 0x2DB400                     | one-time processing grace for this accepted request
    .endif
request_published:
    move.w (%sp)+,%sr
request_handled:
    moveq #1,%d0
request_done:
    movem.l (%sp)+,%d1-%d7/%a0-%a6
    rts

| incoming (2026-10-26-r13, jdc: "sometimes B switched skaters instead of the outlet"):
| between the save and the native ownership ($B7AA still loose) a quick B tap was an ordinary
| switch and the outlet never got it. A loose puck within 24 px of this pad's team goalie
| now counts as his: d2 = goalie index, so the tap is queued (ASK) and served on ownership.
incoming:
    movem.l %d0-%d1/%a0,-(%sp)
    cmpi.w #6,%d4
    bhi.s 9f
    lea 0xFFFFC328,%a0
    move.w (%a0,%d4.w),%d0
    moveq #5,%d1
    cmpi.w #1,%d0
    beq.s 2f
    moveq #11,%d1
    cmpi.w #2,%d0
    bne.s 9f
2:  move.w %d1,%d0
    lsl.w #7,%d0
    lea OBJ,%a0
    adda.w %d0,%a0
    tst.w 0x34(%a0)                    | a goalie in that slot
    bne.s 9f
    move.w 0xFFFFB74A,%d0
    sub.w (%a0),%d0
    bpl.s 3f
    neg.w %d0
3:  cmpi.w #24,%d0
    bhi.s 9f
    move.w 0xFFFFB75E,%d0
    sub.w 0x14(%a0),%d0
    bpl.s 4f
    neg.w %d0
4:  cmpi.w #24,%d0
    bhi.s 9f
    move.w %d1,%d2
9:  movem.l (%sp)+,%d0-%d1/%a0
    rts

select_target:
    move.l %d0,-(%sp)
    cmpi.l #0x4F55544C,REQ+4
    bne.s native_selection
    move.w 0x52(%a3),%d0
    cmp.w REQ+2,%d0
    bne.s native_selection
    move.w REQ,%d0
    subq.w #1,%d0
    cmpi.w #11,%d0
    bhi.s native_selection
    lsl.w #7,%d0
    lea OBJ,%a0
    adda.w %d0,%a0
    movem.l (%sp)+,%d0
    jmp 0xBD64                        | exact target; avoid native rescan choosing a CPU in the same direction
native_selection:
    movem.l (%sp)+,%d0
    moveq #5,%d3                     | original BCDA..BCE5, including flags for branch at BCE6
    movea.w #0xB04A,%a1
    cmpi.w #6,0x52(%a3)
    jmp 0xBCE6

goalie_direction:
    move.l %d0,-(%sp)
    cmpi.l #0x4F55544C,REQ+4
    bne.s native_direction
    move.w 0x52(%a3),%d0
    cmp.w REQ+2,%d0
    bne.s native_direction
    movem.l (%sp)+,%d0
    jmp 0xBDD8                        | retain actual vector to the checked human, including safe side/back passes
native_direction:
    movem.l (%sp)+,%d0
    tst.w 0xFFB774
    btst #7,0x62(%a3)                 | preserve both N from velocity and Z from direction bit
    jmp 0xBDC4
