    .ifndef WITH_REFINEMENTS
    .equ WITH_REFINEMENTS,0
    .endif
    .ifndef WITH_MOMENTUM
    .equ WITH_MOMENTUM,0
    .endif
    .ifndef WITH_EASY_STATS
    .equ WITH_EASY_STATS,0
    .endif
    .ifndef LEGACY_WHISTLE_MOMENTUM
    .equ LEGACY_WHISTLE_MOMENTUM,0
    .endif
    .ifndef WITH_PREF_DEFAULTS
    .equ WITH_PREF_DEFAULTS,1
    .endif
| Crowd momentum meters (jdc 2026-10-25): shots on goal and body checks pull one
| shared momentum value M (-1000..+1000, + = home). Each game-clock second M
| decays by M/128, so the pace follows the chosen period length. Ratings of the
| skaters and goalies on the ice are scaled by M/100 percent (-10..+10): home
| gets +p, away -p. Two bars beside the score box show each team's side.
| Hard now shares Normal momentum; Hard difficulty comes from decision/position AI.
| Easy: WITH_EASY_STATS uses fixed maximum/20-equivalent skills and full human energy.
| Legacy builds retain the percentage bonuses. Eligible direct-human crits remain.
| Entries: +0 frame (sprite builder, replaces movea.w #$C018,a6 / moveq #1,d6),
|          +4 load (tail of the native rating loader, replaces jmp $1FF002),
|          +8 defaults (power-on settings, replaces jsr $2FE200),
|          +12 roster (attribute value for Team Roster, replaces jsr $FA9F8 at $8D70),
|          +16 gameend ($172FA game-RAM reset after every game: the meter starts over),
|          +20 critroll (critical.s, before RNG(1000): rating scaled by the shooter's percent),
|          +24 critshow (critical.s Team Roster Crit column: same scaling),
|          +28 draw (bars at the end of the sprite list, called by trail.s; r7),
|          +32 easylookup (critical.s rating lookup; Easy humans always crit; r27).
    .equ MAG,0xFFF700                  | word 'CR' when the block below is valid
    .equ MOM,0xFFF702                  | word M
    .equ LAST,0xFFF704                 | 4 words: home shots, home checks, away shots, away checks
    .equ SEC,0xFFF70C                  | word: last clock seconds
    .equ PCTH,0xFFF70E                 | word: percent for the $C6CE team
    .equ PCTA,0xFFF72A                 | word: percent for the $CA32 team
    .equ SIDEH,0xFFF72C                | words: bar values -1000..1000
    .equ SIDEA,0xFFF72E
    .equ LPH,0xFFF732                  | words: percents of the last rescale pass
    .equ LPA,0xFFF734
    .equ HUM,0xFFF730                  | bytes: $C6CE team / $CA32 team seen human-controlled
    .equ DIFF,0xFFF07A                 | menu setting: 0 Normal, 1 Hard, 2 Easy (r27)
    .equ APP,0xFFF710                  | 12 bytes: percent applied per object ($7F = reapply)
    .equ VAL,0xFFF71C                  | 12 bytes: base ratings captured
    .equ TICK,0xFFF728                 | word: frame counter for the tile upload
    .equ BASE,0xFFF740                 | 12 x 16 bytes: native +$66..+$75 per object
    .equ HOME,0xFFC6CE
    .equ AWAY,0xFFCA32
    .equ OBJ,0xFFFFB04A                | movea.w #$B04A sign-extends
    .equ TILES,0x2E0800                | 27 tiles written by crowd.py
    .equ TILE,0x7E1                    | VRAM $FC20 (unused tail of the full-screen H-scroll table)
    .equ BX,222+128                    | bar left x (sprite coordinates)
    .equ AY,191+128                    | away bar y (score box lists the $CA32 team first)
    .equ HY,206+128                    | home bar y ($C6CE team, lower score box row)
| r38 (jdc: skaters passed OVER the bars): the bars are HUD-plane cells like the score box,
| not sprites (the native list draws players first, so they covered every sprite of ours).
| The two bars are composed into 8 tiles at $7E5 (VRAM $FCA0, after the shot bar's $7E1-$7E4;
| REPLAY borrows $7E1.. during a replay, then BSHOWN forces a fresh upload) and sent by the native DMA queue; cells
| rows 24 (away) / 26 (home), columns 27-30 of the HUD plane $E000.
    .equ BTILE,0x7E5                   | r39: back in the old crowd area ($FCA0); $FB00 (window rows
                                       | 22-23) showed up as garbage ice/boards when a glass broke
    .equ BBUF,0xFFE900                 | 3 x 128 bytes: away, home, shot meter (menu RAM, free in play)
    .equ BSHOWN,0xFFE8FE               | $B4A5 while our cells are in the HUD plane
    .equ BKEY,0xFFE8F0                 | 3 words: last fill+colour per bar (only changes are composed)
    .ifndef LEGACY_HARD_CROWD
    .equ LEGACY_HARD_CROWD,0
    .endif
    .text
    .globl _start
_start:
    bra.w frame
    bra.w load
    bra.w defaults
    bra.w roster
    bra.w gameend
    bra.w critroll
    bra.w critshow
    bra.w draw
    bra.w easylookup
    bra.w mkbar                        | +36 r38: compose a 32x8 bar into 4 tiles (shotmeter.s too)
    .if WITH_STARS_HUD
    bra.w clear_bars                   | +40 Native Stars entry cleanup before its window draws
    .endif

| d0 = crit rating (0..100), a3 = shooter -> d1 = scaled rating, d0 = 1000
| (replaces move.w d0,d1 / move.w #1000,d0 at $2FF462).
critroll:
    move.w %d0,%d1
    cmpi.w #0x4352,MAG
    bne.s 9f
    move.w PCTH,%d0
    btst #6,0x62(%a3)
    beq.s 1f
    move.w PCTA,%d0
1:  .if WITH_MOMENTUM
    jsr 0x226010
    .endif
    muls.w %d1,%d0
    divs.w #100,%d0
    add.w %d0,%d1
    bpl.s 9f
    moveq #0,%d1
9:  addi.w #30,%d1                     | r26 (jdc): +3 percentage points for every crit-eligible shot
    cmpi.w #2,DIFF                     | (rating 100: 13 %, 50: 8 %); Easy (r27): a human's slapshot
    bne.s 8f                           | always crits (RNG(1000) < 1000)
    btst #3,0x62(%a3)
    beq.s 8f
    move.w #1000,%d1
8:  move.w #1000,%d0
    rts

| +32 easylookup (r27): replaces bsr.w lookup / tst.w d0 at $2FF45A in critical.s. A human in
| Easy counts as rated even with Crit 0, so every forehand slapshot reaches the roll above.
easylookup:
    jsr 0x2FF40C                       | critical.s lookup: d0 = rating
    .if WITH_EASY_STATS
    cmpi.w #2,DIFF
    bne.s 1f
    jsr 0x2EE410
    .else
    cmpi.w #2,DIFF
    bne.s 1f
    btst #3,0x62(%a3)
    beq.s 1f
    moveq #1,%d0
    .endif
1:  tst.w %d0
    rts

| d0 = crit rating for the Team Roster column, a2 = team block (replaces jmp $8E4C).
critshow:
    .if WITH_EASY_STATS
    cmpi.w #2,DIFF
    bne.s easy_crit_native
    jsr 0x2EE40C
    jmp 0x8E4C
easy_crit_native:
    .endif
    cmpi.w #0x4352,MAG
    bne.s 9f
    movem.l %d1-%d2,-(%sp)
    move.w PCTH,%d2
    cmpa.w #0xC6CE,%a2
    beq.s 1f
    move.w PCTA,%d2
    cmpa.w #0xCA32,%a2
    bne.s 2f
1:  .if WITH_MOMENTUM
    jsr 0x226014
    .endif
    move.w %d0,%d1
    muls.w %d2,%d1
    divs.w #100,%d1
    add.w %d1,%d0
    bpl.s 2f
    moveq #0,%d0
2:  movem.l (%sp)+,%d1-%d2
9:  jmp 0x8E4C

| Roster during a game shows the rating with the team's current percent
| (Overall and 0..99 ratings, mode $0A; Handed/Weight/Energy/Fighting/Crit unchanged).
roster:
    jsr 0xFA9F8                        | d0 value, d1 limit, d4 = mode<<16 | mask
    .if WITH_EASY_STATS
    cmpi.w #2,DIFF
    bne.s easy_roster_native
    jmp 0x2EE408
easy_roster_native:
    .endif
    movem.l %d2-%d3,-(%sp)
    cmpi.w #0x4352,MAG
    bne.s 9f
    move.l %d4,%d2
    swap %d2
    cmpi.w #0x0A,%d2
    bne.s 9f
    move.w PCTH,%d2
    cmpa.w #0xC6CE,%a2
    beq.s 1f
    move.w PCTA,%d2
    cmpa.w #0xCA32,%a2
    bne.s 9f
1:  .if WITH_MOMENTUM
    jsr 0x226014
    .endif
    move.w %d0,%d3
    muls.w %d2,%d3
    divs.w #100,%d3
    add.w %d3,%d0
    bpl.s 2f
    moveq #0,%d0
2:  cmp.w %d1,%d0
    blt.s 9f
    move.w %d1,%d0
    subq.w #1,%d0
9:  movem.l (%sp)+,%d2-%d3
    rts

gameend:                               | replaces bsr $169D0 / move.w #$2700,sr at $172FA
    .if WITH_GK_OFFENSE
    jsr 0x2DF410
    .else
    jsr 0x169D0
    .endif
    .if WITH_PASS_PLAN
    jsr 0x2DF808
    .endif
    clr.w MAG                          | next game (or Roster outside a game) starts neutral
    clr.l 0xFFF738                     | replay.s: no replay pending (ARST, ARCNT)
    clr.l 0xFFF73C
    clr.l 0xFFF630                     | trail.s HN/HT and TL: no stale afterglow from menu text
    clr.l 0xFFF634                     | (CNTDN, LASTG; r7: trail TL and GOK)
    clr.l 0xFFF640                     | shotmeter.s magic / shooter (r27)
    move.w #0x2700,%sr
    rts

defaults:
    jsr 0x2FE200                       | fix 10 start-up defaults (replayed)
    clr.l DIFF                         | and mtap.s TM ($FFF07C, r16)
    .if WITH_PREF_DEFAULTS
    move.w #1,DIFF                    | requested default Hard; Easy remains2
    .endif
    jsr 0x2DE004                       | global automatic replay preference, default On
    rts

| ---------------------------------------------------------------- loader tail
load:
    jsr 0x1FF002                       | replayed native/hack tail
    movem.l %d0-%d4/%a0-%a1,-(%sp)
    bsr.w valid
    move.l %a3,%d0
    sub.l #OBJ,%d0
    bcs.s 9f
    moveq #0x7F,%d1
    and.w %d0,%d1
    bne.s 9f
    lsr.l #7,%d0
    cmpi.l #12,%d0
    bcc.s 9f
    lea BASE,%a0
    move.w %d0,%d1
    lsl.w #4,%d1
    adda.w %d1,%a0
    lea 0x66(%a3),%a1
    moveq #15,%d1
1:  move.b (%a1)+,(%a0)+
    dbra %d1,1b
    lea VAL,%a0
    st (%a0,%d0.w)
    lea APP,%a0
    move.b #0x7F,(%a0,%d0.w)
    .if WITH_EASY_STATS
    cmpi.w #2,DIFF
    bne.s load_native_scale
    jsr 0x2EE404
    bra.s 9f
load_native_scale:
    .endif
    bsr.w apply1
9:  movem.l (%sp)+,%d0-%d4/%a0-%a1
    rts

| ------------------------------------------------------------- once per frame
frame:
    movem.l %d0-%d5/%a0-%a4,-(%sp)
    movea.w #0xC018,%a6
    moveq #1,%d6
    bsr.w valid
    .if WITH_MOMENTUM
    cmpi.w #1,0xFFFFF84A
    bne.s momentum_enabled
    jsr 0x22600C
    cmpi.w #2,DIFF
    beq.w 5f                       | independent fixed Easy skills/energy
    bra.w 6f                       | native cache restores all active base ratings
momentum_enabled:
    .endif
    bsr.w events
    bsr.w decay
    move.w MOM,%d0
    .if LEGACY_HARD_CROWD
    tst.w DIFF
    bne.s 5f
    .else
    cmpi.w #2,DIFF                     | only Easy keeps fixed meters; Hard shares Normal
    beq.s 5f
    .endif
    move.w %d0,SIDEH                   | Normal: one shared momentum
    neg.w %d0
    move.w %d0,SIDEA
    move.w MOM,%d0
    ext.l %d0
    divs.w #100,%d0
    move.w %d0,PCTH
    neg.w %d0
    move.w %d0,PCTA
    bra.s 6f
5:  .if WITH_EASY_STATS
    jsr 0x2EE400
    bra.w 8f
    .else
    bsr.w humans                       | Hard: CPU full, human empty
    move.w #1000,%d0
    moveq #10,%d2
    tst.b HUM
    beq.s 4f
    neg.w %d0
    neg.w %d2
4:  move.w %d0,SIDEH
    move.w %d2,PCTH
    move.w #1000,%d0
    moveq #10,%d2
    tst.b HUM+1
    beq.s 3f
    neg.w %d0
    neg.w %d2
3:  move.w %d0,SIDEA
    move.w %d2,PCTA
    cmpi.w #2,DIFF                     | Easy (jdc r27): the mirror - humans full (+10 %), CPU empty
    bne.s 6f
    neg.w SIDEH
    neg.w PCTH
    neg.w SIDEA
    neg.w PCTA
    .endif
6:  move.w PCTH,%d0                    | rescale only when a percent changed (the loader
    move.w PCTA,%d1                    | hook applies it to new players itself)
    cmp.w LPH,%d0
    bne.s 7f
    cmp.w LPA,%d1
    beq.s 8f
7:  move.w %d0,LPH
    move.w %d1,LPA
    moveq #0,%d0
    movea.w #0xB04A,%a3
1:  bsr.w apply1
    adda.w #0x80,%a3
    addq.w #1,%d0
    cmpi.w #12,%d0
    bcs.s 1b
8:
9:  jsr 0x2E4004                       | trail.s: crit flames first in the list, on top of the
    jsr 0x2E5000                       | goalie (only during a crit shot in play: few sprites); replay.s goal watch
    jsr 0x2ED000                       | shotmeter.s: charge / shot speed (r27)
    movem.l (%sp)+,%d0-%d5/%a0-%a4
    rts

| +28 draw (2026-10-26-r7): the bars at the END of the sprite list, from trail.s's hook at
| $15F06 (a6/d6 = the native list). Drawn first (r24) they pushed the game's own sprites
| past the list's end $C2E8 in the ~90-sprite game intro, over $C2EC (pause flag):
| "Resume Game" froze before the faceoff. sprs stops at 80 sprites ($C298).
draw:                                  | A5 is the native DMA queue
    .if WITH_REFINEMENTS
    jmp 0x259000
clear_bars:
    jmp 0x259004
    .else
    btst #7,0xFFC2EC
    bne.w foreign_view
    .if WITH_MOMENTUM
    cmpi.w #1,0xFFFFF84A
    beq.w clear_bars
    .endif
    bsr.w inreplay                     | no meters during the automatic replay (r23)
    bcs.w 8f
    movem.l %d0-%d6/%a0-%a1,-(%sp)
    cmpi.w #0xB4A5,BSHOWN              | first frame (game start, after a replay): compose both
    beq.s 1f
    move.l #-1,BKEY
1:  moveq #0,%d6                       | bit0/1: a bar changed
    move.w SIDEA,%d0
    lea BBUF,%a0
    lea BKEY,%a1
    bsr.w crowdbar                     | d4 = palette bits
    move.w %d4,%d5
    move.w SIDEH,%d0
    lea BBUF+128,%a0
    lea BKEY+2,%a1
    bsr.w crowdbar
    tst.w %d6
    beq.s 2f
    move.l #BBUF,(%a5)+                | 8 tiles, only when one changed
    move.w #128,(%a5)+
    move.w #BTILE*32,(%a5)+
2:  move.w %sr,-(%sp)
    ori.w #0x700,%sr
    lea 0xC00000,%a1
    move.w #0x8F02,0xC00004            | auto-increment 2
    move.l #0x60000003+((24*64+27)*2<<16),0xC00004
    move.w #0x8000+BTILE,%d0
    or.w %d5,%d0
    bsr.w cells4
    move.l #0x60000003+((26*64+27)*2<<16),0xC00004
    move.w #0x8000+BTILE+4,%d0
    or.w %d4,%d0
    bsr.w cells4
    move.w (%sp)+,%sr
    move.w #0xB4A5,BSHOWN
    movem.l (%sp)+,%d0-%d6/%a0-%a1
    rts
foreign_view:                          | horizontal penalty/ticker/menus own these map cells
    clr.w BSHOWN                       | do not erase the native overlay after it drew
    rts
clear_bars:
8:  cmpi.w #0xB4A5,BSHOWN              | replay / other view: our cells go, once
    bne.s 9f
    clr.w BSHOWN
    movem.l %d0/%a1,-(%sp)
    move.w %sr,-(%sp)
    ori.w #0x700,%sr
    lea 0xC00000,%a1
    move.w #0x07FF,%d0
    move.w #0x8F02,0xC00004
    move.l #0x60000003+((24*64+27)*2<<16),0xC00004
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.l #0x60000003+((26*64+27)*2<<16),0xC00004
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.w %d0,(%a1)
    move.w (%sp)+,%sr
    movem.l (%sp)+,%d0/%a1
9:  rts
cells4:                                | d0 = first map word -> four consecutive tiles
    move.w %d0,(%a1)
    addq.w #1,%d0
    move.w %d0,(%a1)
    addq.w #1,%d0
    move.w %d0,(%a1)
    addq.w #1,%d0
    move.w %d0,(%a1)
    rts
crowdbar:                              | d0 = side -1000..1000, a0 = 128 bytes, a1 = its key
    move.w %d0,%d4                     | -> d4 palette bits, d6 nonzero if composed
    add.w #1000,%d0
    mulu.w #3,%d0
    divu.w #200,%d0                    | 0..30 px
    moveq #1,%d1                       | white on black (palette 0)
    moveq #4,%d2
    tst.w %d4
    bpl.s 1f
    moveq #0xA,%d1                     | red on black (palette 1: A red, 7 black, 1 light grey)
    moveq #7,%d2
1:  moveq #1,%d3                       | neutral tick at x 16
    move.w %d1,%d4
    lsl.w #8,%d4
    or.w %d0,%d4
    cmp.w (%a1),%d4
    beq.s 3f
    move.w %d4,(%a1)
    moveq #1,%d6
    bsr.s mkbar
3:
    moveq #0,%d4
    cmpi.w #0xA,%d1
    bne.s 2f
    move.w #0x2000,%d4
2:  rts

    .endif

| mkbar: d0 = fill 0..30 px (x 1..fill), d1 = fill colour, d2 = background colour, d3 bit0 = tick
| (x 16 background in rows 1-6, colour 1 in rows 0/7), bit1 = rows 0/7 solid background (the shot
| bar inside its black panel: no see-through lines, r39), a0 = 4 tiles (128 bytes).
| Rows 0/7 transparent, 1/6 background, 2-5 fill - the old sprite bar exactly.
mkbar:
    movem.l %d0-%d7/%a0-%a1,-(%sp)
    moveq #0,%d6
    move.w %d2,%d6
    mulu.w #0x1111,%d6
    move.w %d6,%d7
    swap %d6
    move.w %d7,%d6
    movea.l %d6,%a1                    | background row
    moveq #0,%d5                       | tile x base
1:  moveq #0,%d4
    moveq #7,%d7
    move.w %d5,%d6
2:  lsl.l #4,%d4
    cmpi.w #1,%d6
    blt.s 4f
    cmp.w %d0,%d6
    bgt.s 4f
    btst #0,%d3
    beq.s 3f
    cmpi.w #16,%d6
    beq.s 4f
3:  or.b %d1,%d4
    bra.s 5f
4:  or.b %d2,%d4
5:  addq.w #1,%d6
    dbra %d7,2b
    moveq #0,%d7                       | rows 0/7
    btst #1,%d3
    beq.s 61f
    move.l %a1,%d7                     | solid: background colour
    bra.s 6f
61: btst #0,%d3
    beq.s 6f
    cmpi.w #16,%d5
    bne.s 6f
    move.l #0x10000000,%d7
6:  move.l %d7,(%a0)+
    move.l %a1,(%a0)+
    move.l %d4,(%a0)+
    move.l %d4,(%a0)+
    move.l %d4,(%a0)+
    move.l %d4,(%a0)+
    move.l %a1,(%a0)+
    move.l %d7,(%a0)+
    addq.w #8,%d5
    cmpi.w #32,%d5
    blt.s 1b
    movem.l (%sp)+,%d0-%d7/%a0-%a1
    rts

inreplay:                              | C set while replay.s plays its automatic replay ($A502..$A506)
    move.l %d0,-(%sp)
    move.w 0xFFF738,%d0
    subi.w #0xA502,%d0
    .if WITH_STARS_HUD
    cmpi.w #6,%d0                      | A502..A506 replay, A507 native postgame HUD ownership
    .else
    cmpi.w #5,%d0                      | 0..4 -> C (unsigned below 5)
    .endif
    movem.l (%sp)+,%d0                 | (movem keeps the flags)
    rts

valid:
    cmpi.w #0x4352,MAG
    beq.s 9f
    move.w #0x4352,MAG
    clr.w MOM
    clr.w PCTH
    clr.w PCTA
    clr.w SIDEH
    clr.w SIDEA
    clr.w HUM
    clr.w LPH
    clr.w LPA
    clr.w TICK
    clr.w 0xFFF736                     | onetimer PEND and replay ARST/ARCNT/CNTDN/LASTG start clean in a game
    clr.l 0xFFF738                     | (menus write text over $FFF7xx; 2026-10-26-r3)
    clr.l 0xFFF73C
    clr.l 0xFFF630                     | r37: trail.s HN/HT and TL/GOK start clean too (season menu
    clr.l 0xFFF634                     | text left HT 3348: no flame tiles for minutes of crits)
    lea APP,%a0
    moveq #23,%d1
1:  clr.b (%a0)+                        | APP and VAL
    dbra %d1,1b
    bsr.w snap
    move.w 0xFFC468,SEC
    jsr 0x2EB034                       | r36: seasonplayers.s keeps the season's Difficulty
9:  rts

snap:                                  | LAST = current team counters
    move.w HOME,LAST
    move.w HOME+0x10,LAST+2
    move.w AWAY,LAST+4
    move.w AWAY+0x10,LAST+6
    rts

events:
    move.w HOME,%d0
    add.w HOME+0x10,%d0
    add.w AWAY,%d0
    add.w AWAY+0x10,%d0
    move.w LAST,%d1
    add.w LAST+2,%d1
    add.w LAST+4,%d1
    add.w LAST+6,%d1
    cmp.w %d1,%d0
    .if LEGACY_WHISTLE_MOMENTUM
    bcc.s 1f
    .else
    beq.s 1f                          | unchanged counters: same taken-branch cost as r7
    bcc.w changed_events
    .endif
    clr.w MOM                          | counters went down: a new game
    clr.w HUM
    bra.w snap
1:
credit_events:
    move.w MOM,%d3
    move.w HOME,%d0
    sub.w LAST,%d0
    mulu.w #200,%d0
    add.w %d0,%d3
    move.w HOME+0x10,%d0
    sub.w LAST+2,%d0
    mulu.w #150,%d0
    add.w %d0,%d3
    move.w AWAY,%d0
    sub.w LAST+4,%d0
    mulu.w #200,%d0
    sub.w %d0,%d3
    move.w AWAY+0x10,%d0
    sub.w LAST+6,%d0
    mulu.w #150,%d0
    sub.w %d0,%d3
    cmpi.w #1000,%d3
    ble.s 2f
    move.w #1000,%d3
2:  cmpi.w #-1000,%d3
    bge.s 3f
    move.w #-1000,%d3
3:  move.w %d3,MOM
    bra.w snap

humans:                                | remember which side has a human-controlled object
    movea.w #0xB04A,%a0
    moveq #11,%d1
1:  btst #3,0x62(%a0)
    beq.s 2f
    lea HUM,%a1
    btst #6,0x62(%a0)
    beq.s 3f
    addq.w #1,%a1
3:  st (%a1)
2:  adda.w #0x80,%a0
    dbra %d1,1b
    rts

decay:
    move.w 0xFFC468,%d0
    cmp.w SEC,%d0
    beq.s 9f
    move.w %d0,SEC
    move.w MOM,%d0
    ext.l %d0
    divs.w #128,%d0
    bne.s 1f
    moveq #1,%d0                       | at least one step toward zero
    tst.w MOM
    bgt.s 1f
    beq.s 9f
    moveq #-1,%d0
1:  sub.w %d0,MOM
9:  rts

| a3 object, d0 index 0..11: write base*(100+p)/100 when p changed
apply1:
    movem.l %d0-%d4/%a0-%a2,-(%sp)
    lea VAL,%a0
    tst.b (%a0,%d0.w)
    beq.s 9f
    move.w PCTH,%d4
    btst #6,0x62(%a3)                  | $CA32 team (native stats block select)
    beq.s 1f
    move.w PCTA,%d4
1:  .if WITH_MOMENTUM
    jsr 0x226018
    .endif
    lea APP,%a0
    cmp.b (%a0,%d0.w),%d4
    beq.s 9f
    move.b %d4,(%a0,%d0.w)
    lea BASE,%a1
    lsl.w #4,%d0
    adda.w %d0,%a1
    lea offs(%pc),%a2
2:  move.b (%a2)+,%d0                  | offset from +$66, bit 6 = lower is better
    bmi.s 9f
    move.w %d4,%d3
    bclr #6,%d0
    beq.s 3f
    neg.w %d3
3:  ext.w %d0
    moveq #0,%d1
    move.b (%a1,%d0.w),%d1
    move.w %d1,%d2
    muls.w %d3,%d2
    divs.w #100,%d2
    add.w %d1,%d2
    bpl.s 4f
    moveq #0,%d2
4:  moveq #30,%d3                     | ceiling 30, or the base if it is higher
    cmp.w %d1,%d3
    bge.s 6f
    move.w %d1,%d3
6:  cmp.w %d3,%d2
    ble.s 5f
    move.w %d3,%d2
5:  move.b %d2,0x66(%a3,%d0.w)
    bra.s 2b
9:  movem.l (%sp)+,%d0-%d4/%a0-%a2
    rts
| ratings: $68..$6D, $6E, $70..$73, $75 ($6A/$6B are inverted timings); keep
| roster slot $66, weight $67, jersey $6F and fighting $74 in legacy scaling.
offs: .byte 0x02,0x03,0x44,0x45,0x06,0x07,0x08,0x0A,0x0B,0x0C,0x0D,0x0F,0xFF
    .even

| d0 = side value (-1000..1000), d2 = y. Sprites: tick, fill, partial, background.

    .if !LEGACY_WHISTLE_MOMENTUM
| Only changed counters reach this guard. Consume blocked events immediately,
| leaving earned M intact and preventing a deferred award on the next faceoff.
changed_events:
    move.b 0xFFFFC2EA,%d0
    andi.b #0x51,%d0                  | clock stopped / highlight / game over
    bne.w snap
    move.b 0xFFFFC2EC,%d0
    andi.b #0x81,%d0                  | pause or foreign view
    bne.w snap
    btst #2,0xFFFFC2F2                | faceoff setup
    bne.w snap
    move.w 0xFFF738,%d0
    subi.w #0xA502,%d0
    cmpi.w #6,%d0                     | replay or Stars overlay
    bcs.w snap
| Live deltas combine the two sides before multiplication. Word arithmetic is
| identical, including wrapped deltas; absolute-short RAM operands save cycles.
| Together with the fixed padding this preserves r7 live-event execution time.
    move.w 0xFFFFF702,%d3
    move.w 0xFFFFC6CE,%d0
    sub.w 0xFFFFF704,%d0
    sub.w 0xFFFFCA32,%d0
    add.w 0xFFFFF708,%d0
    mulu.w #200,%d0
    add.w %d0,%d3
    move.w 0xFFFFC6DE,%d0
    sub.w 0xFFFFF706,%d0
    sub.w 0xFFFFCA42,%d0
    add.w 0xFFFFF70A,%d0
    mulu.w #150,%d0
    add.w %d0,%d3
    cmpi.w #1000,%d3
    ble.s live_low
    move.w #1000,%d3
live_low:
    cmpi.w #-1000,%d3
    bge.s live_store
    move.w #-1000,%d3
live_store:
    move.w %d3,0xFFFFF702
    .rept 11
    nop
    .endr
    move.w 0xFFFFC6CE,0xFFFFF704
    move.w 0xFFFFC6DE,0xFFFFF706
    move.w 0xFFFFCA32,0xFFFFF708
    move.w 0xFFFFCA42,0xFFFFF70A
    rts
    .endif
