    .ifndef WITH_MANUAL_KEEPER_PASS
    .equ WITH_MANUAL_KEEPER_PASS,1
    .endif
| 4-Way Play goalie hold/request/Y, preserving native P3/P4 input-bank swaps.
| +0 early B, +4 legacy B, +8 Y reader, +12 Y handler, +16 cooperative sides,
| +20 new-game reset. Native D4 is always0/2; C318>=0 identifies P3/P4.
    .equ YREQ,0xFFF0B2              | four bytes at even offsets0/2/4/6
    .equ YHELD,0xFFF0BA             | four held-Y states, same offsets
    .equ BHELD,0xFFF0C2             | four words: consume B until released
    .equ MAGIC,0xFFF0CA
    .equ ASK,0xFFF064
    .equ OBJ,0xFFFFB04A
    .text
    .globl _start
_start:
    bra.w early
    bra.w legacy
    bra.w reader
    bra.w handler
    bra.w cooperative
    bra.w reset
reset:
    clr.w MAGIC
    clr.l ASK
    rts
init:
    cmpi.w #0x3447,MAGIC
    beq.s 9f
    clr.l YREQ
    clr.l YREQ+4
    clr.l YHELD
    clr.l YHELD+4
    clr.l BHELD
    clr.l BHELD+4
    move.w #0x3447,MAGIC
9:  rts

| Z only when at least one human is present and every active pad is on one side.
| Opposing human teams retain the native adapter behaviour. Registers preserved.
cooperative:
    movem.l %d0-%d2/%a0,-(%sp)
    lea 0xFFFFC328,%a0
    moveq #0,%d0
    moveq #3,%d2
1:  move.w (%a0)+,%d1
    beq.s 3f
    cmpi.w #2,%d1
    bhi.s 8f
    tst.w %d0
    beq.s 2f
    cmp.w %d0,%d1
    bne.s 8f
2:  move.w %d1,%d0
3:  dbra %d2,1b
    tst.w %d0
    beq.s 8f
    moveq #0,%d0
    bra.s 9f
8:  moveq #-1,%d0
9:  movem.l (%sp)+,%d0-%d2/%a0
    rts

allowed:
    bsr.s cooperative
    .if WITH_IMMEDIATE_B
    bne.s b_noncooperative
    .else
    bne.s 8f
    .endif
allowed_states:
    btst #0,0xFFFFC2FA
    bne.s 8f
    tst.w 0xFFFFD412
    bne.s 8f
    btst #2,0xFFFFC2F2
    bne.s 8f
    btst #3,0x62(%a3)
    beq.s 8f
    btst #0,0x63(%a3)
    bne.s 8f
    moveq #0,%d0
    rts
8:  moveq #-1,%d0
    rts
    .if WITH_IMMEDIATE_B
b_noncooperative:
    btst #4,%d1
    beq.s 8b
    bra.w allowed_states
    .endif
realpad:                             | input handler bank -> physical offset, D0 only
    move.w %d4,%d0
    tst.w 0xFFFFC318
    bmi.s 9f
    addq.w #4,%d0
9:  rts
early:
    tst.w 0xFFFFD046
    beq.w oldearly
    movem.l %d0-%d7/%a0-%a6,-(%sp)
    bsr.w init
    bsr.s allowed
    bne.w native_early
    bsr.s realpad
    move.w %d0,%d4
    lea YREQ,%a0
    tst.b (%a0,%d4.w)
    bne.w native_early              | explicit Y wins; its reader did not synthesize B
    jsr 0x2FDC0C
    tst.w %d0
    beq.w 1f
    .if WITH_IMMEDIATE_B
    cmpi.w #2,%d0
    beq.w fast
    .endif
    lea BHELD,%a0
    move.w #1,(%a0,%d4.w)
    bra.w consume
1:  lea BHELD,%a0
    tst.w (%a0,%d4.w)
    beq.s flight
    btst #4,%d3
    bne.w consume
    clr.w (%a0,%d4.w)
    bra.w consume
flight:                              | do not switch while a pass from your goalie is travelling
    tst.w 0xFFFFB7AA
    bpl.s fast
    move.w 0xFFFFBEDA,%d0
    cmpi.w #5,%d0
    beq.w 2f
    cmpi.w #11,%d0
    bne.s fast
2:  lsl.w #7,%d0
    lea OBJ,%a0
    move.b 0x62(%a0,%d0.w),%d0
    move.b 0x62(%a3),%d2
    eor.b %d2,%d0
    btst #6,%d0
    bne.s fast
    bra.w consume
| fast (2026-10-26-r22, jdc): with the adapter there was no B switch at all for co-op pads
| (the native held-B switch is off so that Y takes the keeper). A new B press, when this
| pad's player is not the puck carrier and no pass is in progress, selects the free skater
| of the pad's side nearest the puck (no goalie, no other human's player, not knocked
| down), also from a human goalie. D4 = physical pad 0..6 here; native assignment C0BC /
| C0DA keeps the bank's 0/2 (P3/P4 run in a swapped P1/P2 bank, copied back by the game).
fast:
    btst #4,%d1
    beq.w native_early
    move.w 0x52(%a3),%d0
    cmp.w 0xFFFFB7AA,%d0
    .if WITH_IMMEDIATE_B
    .if WITH_MANUAL_KEEPER_PASS
    beq.w native_early              | owning manual keeper must reach native B72E/BC06 passing
    .else
    bne.s fast_without_possession
    tst.w 0x34(%a3)
    bne.w native_early
    .endif
fast_without_possession:
    .else
    beq.w native_early
    btst #3,0x63(%a3)
    bne.w native_early
    .endif
    move.w %d4,%d7                   | physical pad
    andi.w #2,%d7                    | the bank's own offset
    .if WITH_IMMEDIATE_B
    move.w 0xFFFFB74A,%d2
    move.w 0xFFFFB75E,%d3
    .else
    move.w 0xFFFFB772,%d2            | puck, a few frames ahead (as switch.s)
    asr.w #8,%d2
    add.w 0xFFFFB74A,%d2
    move.w 0xFFFFB774,%d3
    asr.w #8,%d3
    add.w 0xFFFFB75E,%d3
    .endif
    lea OBJ,%a0
    lea 0xFFFFC328,%a1
    cmpi.w #1,(%a1,%d7.w)
    beq.s 3f
    cmpi.w #2,(%a1,%d7.w)
    bne.w native_early
    adda.w #0x300,%a0
3:  moveq #-1,%d5                    | best object
    moveq #-1,%d6                    | best squared distance
    swap %d7
    move.w #5,%d7
4:  tst.w 0x34(%a0)
    ble.s 5f                         | goalie / injured / unavailable slot
    move.w 0x52(%a0),%d0
    cmp.w 0x52(%a3),%d0
    beq.s 5f                         | another player, not the same one
    btst #2,0x63(%a0)
    bne.s 5f
    btst #3,0x62(%a0)
    bne.s 5f                         | never another human's player
    btst #5,0x62(%a0)
    bne.s 5f                         | knocked down / unavailable
    move.w %d2,%d0
    sub.w (%a0),%d0
    muls.w %d0,%d0
    move.w %d3,%d1
    sub.w 0x14(%a0),%d1
    muls.w %d1,%d1
    add.l %d1,%d0
    cmp.l %d6,%d0
    bhi.s 5f
    move.l %d0,%d6
    move.w 0x52(%a0),%d5
5:  adda.w #0x80,%a0
    dbra %d7,4b
    swap %d7
    tst.w %d5
    bmi.w native_early               | nobody free: as before
    .if WITH_IMMEDIATE_B
    move.w %sr,-(%sp)
    ori.w #0x700,%sr
    move.b 0x63(%a3),-(%sp)
    bclr #3,0x63(%a3)                 | Native assignment otherwise refuses the released pass animation
    .endif
    move.w %d5,%d0
    tst.w %d7
    bne.s 6f
    jsr 0xC0BC
    bra.s 7f
6:  jsr 0xC0DA
7:
    .if WITH_IMMEDIATE_B
    move.b (%sp)+,0x63(%a3)           | preserve the actual in-flight pass/animation state
    move.w (%sp)+,%sr
    | A brief B tap must also publish the outlet for the newly selected skater.
    | Use the original physical press, even if the hold ends before its next tick.
    move.l 4(%sp),%d1
    move.l 12(%sp),%d3
    bclr #4,%d1
    ori.w #16,%d3
    move.l %a3,-(%sp)
    move.w %d5,%d0
    lsl.w #7,%d0
    lea 0xFFFFB04A,%a3
    adda.w %d0,%a3
    jsr 0x2FDC0C
    movea.l (%sp)+,%a3
    .endif
    lea BHELD,%a0                    | consume this press until B is released
    move.w #1,(%a0,%d4.w)
    movem.l (%sp)+,%d0-%d7/%a0-%a6
    rts                              | the old object's input handler ends after selection
consume:
    movem.l (%sp)+,%d0-%d7/%a0-%a6
    bclr #4,%d1
    bclr #4,%d2
    bclr #4,%d3
    bra.s original_flags
native_early:
    movem.l (%sp)+,%d0-%d7/%a0-%a6
original_flags:
    btst #3,0x63(%a3)
    bne.s 1f
    jmp 0xB1C0
1:  jmp 0xBA88
oldearly:
    .if WITH_IMMEDIATE_B
    jmp 0x2DB000
    .else
    jmp 0x2FD900                     | original two-pad fast-switch code and RAM stay intact
    .endif
legacy:
    tst.w 0xFFFFD046
    beq.s 9f
    move.l %d0,-(%sp)
    bsr.w allowed
    tst.w %d0
    movem.l (%sp)+,%d0
    bne.s original_b
    jmp 0xB42E                       | co-op adapter: Y, not a held B, explicitly takes the keeper
original_b:
    btst #4,%d3
    beq.s 1f
    jmp 0xB2E2
1:  jmp 0xB35C
9:
    .if WITH_IMMEDIATE_B
    jmp 0x2DB004
    .else
    jmp 0x2FD904
    .endif

reader:
    tst.w 0xFFFFD046
    beq.w oldreader
    .if WITH_QUICKGAME
    tst.w 0xFFFFF84C
    bne.s quick_reader
    .endif
    bsr.w cooperative
    bne.w oldreader
    .if WITH_QUICKGAME
quick_reader:
    .endif
    movem.l %d3/%a1,-(%sp)
    bsr.w init
    moveq #0,%d0
    move.b 0xFFFFBF06,%d0          | hardware reader's bits7:6 select virtual pad0..3
    lsr.w #6,%d0
    add.w %d0,%d0
    bsr.w latch_b                   | keep a brief hardware B request even between skater input updates
    lea YREQ,%a1
    btst #1,%d2
    bne.w yrelease
    tst.b 8(%a1,%d0.w)
    bmi.w yreturn
    bne.w yasb
    btst #0,0xFFFFC2FA             | native shootout/penalty-shot controls remain untouched
    bne.w yasb
    tst.w 0xFFFFD412
    bne.w yasb
    lea 0xFFFFC328,%a1
    move.w (%a1,%d0.w),%d3
    beq.w yasb
    cmpi.w #2,%d3
    bhi.w yasb
    subq.w #1,%d3
    add.w %d3,%d3
    lea 0xFFFFD05A,%a1
    tst.w (%a1,%d3.w)              | goalie control is per side, not per physical pad
    bne.w yasb
    lea 0xFFFFC320,%a1
    move.w (%a1,%d0.w),%d3
    bmi.w yasb
    cmpi.w #11,%d3
    bhi.w yasb
    move.w 0xFFFFB7AA,%d2
    bmi.s ywant
    cmp.w %d2,%d3                  | r24 (jdc): Y stays a pass only with YOUR puck or your own
    beq.s yoldb                    | goalie's (the outlet); with a teammate skater's puck Y took
    cmpi.w #5,%d2                  | B's place and (r22) switched skaters instead of the keeper
    beq.s 1f
    cmpi.w #11,%d2
    bne.s ywant
1:  subq.w #6,%d2
    subq.w #6,%d3
    eor.w %d2,%d3
    bpl.s yoldb
ywant:
    clr.l ASK                       | an explicit manual-Y request wins over a queued outlet
    lea YREQ,%a1
    st (%a1,%d0.w)
    st 8(%a1,%d0.w)
    bra.s yreturn
yoldb:
    lea YHELD,%a1
    move.b #1,(%a1,%d0.w)
    bra.s yasb
yrelease:
    clr.b (%a1,%d0.w)
    clr.b 8(%a1,%d0.w)
yreturn:
    movem.l (%sp)+,%d3/%a1
    jmp 0x1FFBF0
yasb:
    bclr #4,%d1
    bsr.w latch_b
    movem.l (%sp)+,%d3/%a1
    jmp 0x1FFC0A
oldreader:
    jmp 0x2FE040

| Raw active-low B in D1, physical pad offset in D0. Called while polling that
| adapter pad, before the native game can replace a one-frame input sample.
| Only requests a human-side AI keeper already holding/within24 px of the puck.
latch_b:
    movem.l %d0-%d5/%a0-%a1,-(%sp)
    move.w %sr,-(%sp)
    btst #4,%d1
    bne.w latch_done
    btst #7,0xFFFFC2EC               | raw menu/replay buttons must not become deferred game requests
    bne.w latch_done
    btst #0,0xFFFFC2EC
    bne.w latch_done
    btst #0,0xFFFFC2FA
    bne.w latch_done
    tst.w 0xFFFFD412
    bne.w latch_done
    move.b 0xFFFFC2EA,%d1
    andi.b #0x11,%d1
    bne.w latch_done
    btst #2,0xFFFFC2F2
    bne.w latch_done
    btst #3,0xFFFFC2F6
    bne.w latch_done
    lea 0xFFFFC328,%a1
    move.w (%a1,%d0.w),%d1
    moveq #5,%d2
    cmpi.w #1,%d1
    beq.s 1f
    moveq #11,%d2
    cmpi.w #2,%d1
    bne.w latch_done
1:  lea 0xFFFFC320,%a1
    move.w (%a1,%d0.w),%d3
    cmpi.w #11,%d3
    bhi.w latch_done
    lsl.w #7,%d3
    lea OBJ,%a1
    tst.w 0x34(%a1,%d3.w)
    ble.w latch_done
    btst #3,0x62(%a1,%d3.w)
    beq.w latch_done
    move.w %d2,%d3
    lsl.w #7,%d3
    lea (%a1,%d3.w),%a0
    tst.w 0x34(%a0)
    bne.w latch_done
    btst #3,0x62(%a0)
    bne.w latch_done
    move.w 0xFFFFB7AA,%d3
    cmp.w %d2,%d3
    beq.s 4f
    tst.w %d3
    bpl.s latch_done
    move.w 0xFFFFB74A,%d3
    sub.w (%a0),%d3
    bpl.s 2f
    neg.w %d3
2:  cmpi.w #24,%d3
    bhi.s latch_done
    move.w 0xFFFFB75E,%d3
    sub.w 0x14(%a0),%d3
    bpl.s 3f
    neg.w %d3
3:  cmpi.w #24,%d3
    bhi.s latch_done
4:  ori.w #0x700,%sr                 | check-and-publish together: first valid hardware request wins
    cmp.w ASK+2,%d2
    bne.s 5f
    tst.w ASK
    bne.s latch_done
5:  addq.w #1,%d0
    move.w %d0,ASK
    move.w %d2,ASK+2
    .if WITH_OUTLET_DEADLINE
    jsr 0x2DB400                     | first hardware publication only; holds never refresh it
    .endif
latch_done:
    move.w (%sp)+,%sr
    movem.l (%sp)+,%d0-%d5/%a0-%a1
    rts

handler:
    tst.w 0xFFFFD046
    beq.w oldhandler
    .if WITH_QUICKGAME
    tst.w 0xFFFFF84C
    bne.s quick_handler
    .endif
    bsr.w cooperative
    bne.w oldhandler
    .if WITH_QUICKGAME
quick_handler:
    .endif
    movem.l %d0-%d1/%a1,-(%sp)
    bsr.w realpad
    lea YREQ,%a1
    tst.b (%a1,%d0.w)
    beq.w no_y
    clr.b (%a1,%d0.w)
    lea BHELD,%a1
    clr.w (%a1,%d0.w)
    tst.w 0xFFFFD412
    bne.w no_y
    btst #0,0xFFFFC2FA
    bne.w no_y
    move.w 0xFFFFB7AA,%d0
    bmi.s 1f
    cmp.w %d6,%d0                 | r24: refuse while this player carries the puck or your own
    beq.s no_y                    | goalie holds it (Y is then the outlet request)
    cmpi.w #5,%d0
    beq.s 4f
    cmpi.w #11,%d0
    bne.s 1f
4:  move.w %d6,%d1
    subq.w #6,%d0
    subq.w #6,%d1
    eor.w %d1,%d0
    bpl.s no_y
1:  moveq #5,%d0
    cmpi.w #5,%d6
    ble.s 2f
    moveq #11,%d0
2:  jsr 0xB86A
    tst.w %d0
    bmi.s no_y
    move.w %d0,%d1
    lsl.w #7,%d1
    lea OBJ,%a1
    btst #3,0x62(%a1,%d1.w)
    bne.s no_y                     | never steal a keeper already controlled by another pad
    clr.l ASK
    addq.l #4,%sp
    movem.l (%sp)+,%d1/%a1
    tst.w %d4                     | physical P3/P4 use the native temporary C320/C322 bank
    bne.s 3f
    jmp 0xC0BC
3:  jmp 0xC0DA
no_y:
    movem.l (%sp)+,%d0-%d1/%a1
    jmp 0x20011A
oldhandler:
    jmp 0x2FE100
