experimental/crowd.s

Download file

Release 2026-11-04-r2 · SHA256 c47298df00695e06046d5bc2d9bdf0fa9b9fd5c551400ebf7bd28f0c55c9732b · Click a line number for a direct link.

1    .ifndef WITH_REFINEMENTS2    .equ WITH_REFINEMENTS,03    .endif4    .ifndef WITH_MOMENTUM5    .equ WITH_MOMENTUM,06    .endif7    .ifndef WITH_EASY_STATS8    .equ WITH_EASY_STATS,09    .endif10    .ifndef LEGACY_WHISTLE_MOMENTUM11    .equ LEGACY_WHISTLE_MOMENTUM,012    .endif13    .ifndef WITH_PREF_DEFAULTS14    .equ WITH_PREF_DEFAULTS,115    .endif16| Crowd momentum meters (jdc 2026-10-25): shots on goal and body checks pull one17| shared momentum value M (-1000..+1000, + = home). Each game-clock second M18| decays by M/128, so the pace follows the chosen period length. Ratings of the19| skaters and goalies on the ice are scaled by M/100 percent (-10..+10): home20| gets +p, away -p. Two bars beside the score box show each team's side.21| Hard now shares Normal momentum; Hard difficulty comes from decision/position AI.22| Easy: WITH_EASY_STATS uses fixed maximum/20-equivalent skills and full human energy.23| Legacy builds retain the percentage bonuses. Eligible direct-human crits remain.24| Entries: +0 frame (sprite builder, replaces movea.w #$C018,a6 / moveq #1,d6),25|          +4 load (tail of the native rating loader, replaces jmp $1FF002),26|          +8 defaults (power-on settings, replaces jsr $2FE200),27|          +12 roster (attribute value for Team Roster, replaces jsr $FA9F8 at $8D70),28|          +16 gameend ($172FA game-RAM reset after every game: the meter starts over),29|          +20 critroll (critical.s, before RNG(1000): rating scaled by the shooter's percent),30|          +24 critshow (critical.s Team Roster Crit column: same scaling),31|          +28 draw (bars at the end of the sprite list, called by trail.s; r7),32|          +32 easylookup (critical.s rating lookup; Easy humans always crit; r27).33    .equ MAG,0xFFF700                  | word 'CR' when the block below is valid34    .equ MOM,0xFFF702                  | word M35    .equ LAST,0xFFF704                 | 4 words: home shots, home checks, away shots, away checks36    .equ SEC,0xFFF70C                  | word: last clock seconds37    .equ PCTH,0xFFF70E                 | word: percent for the $C6CE team38    .equ PCTA,0xFFF72A                 | word: percent for the $CA32 team39    .equ SIDEH,0xFFF72C                | words: bar values -1000..100040    .equ SIDEA,0xFFF72E41    .equ LPH,0xFFF732                  | words: percents of the last rescale pass42    .equ LPA,0xFFF73443    .equ HUM,0xFFF730                  | bytes: $C6CE team / $CA32 team seen human-controlled44    .equ DIFF,0xFFF07A                 | menu setting: 0 Normal, 1 Hard, 2 Easy (r27)45    .equ APP,0xFFF710                  | 12 bytes: percent applied per object ($7F = reapply)46    .equ VAL,0xFFF71C                  | 12 bytes: base ratings captured47    .equ TICK,0xFFF728                 | word: frame counter for the tile upload48    .equ BASE,0xFFF740                 | 12 x 16 bytes: native +$66..+$75 per object49    .equ HOME,0xFFC6CE50    .equ AWAY,0xFFCA3251    .equ OBJ,0xFFFFB04A                | movea.w #$B04A sign-extends52    .equ TILES,0x2E0800                | 27 tiles written by crowd.py53    .equ TILE,0x7E1                    | VRAM $FC20 (unused tail of the full-screen H-scroll table)54    .equ BX,222+128                    | bar left x (sprite coordinates)55    .equ AY,191+128                    | away bar y (score box lists the $CA32 team first)56    .equ HY,206+128                    | home bar y ($C6CE team, lower score box row)57| r38 (jdc: skaters passed OVER the bars): the bars are HUD-plane cells like the score box,58| not sprites (the native list draws players first, so they covered every sprite of ours).59| The two bars are composed into 8 tiles at $7E5 (VRAM $FCA0, after the shot bar's $7E1-$7E4;60| REPLAY borrows $7E1.. during a replay, then BSHOWN forces a fresh upload) and sent by the native DMA queue; cells61| rows 24 (away) / 26 (home), columns 27-30 of the HUD plane $E000.62    .equ BTILE,0x7E5                   | r39: back in the old crowd area ($FCA0); $FB00 (window rows63                                       | 22-23) showed up as garbage ice/boards when a glass broke64    .equ BBUF,0xFFE900                 | 3 x 128 bytes: away, home, shot meter (menu RAM, free in play)65    .equ BSHOWN,0xFFE8FE               | $B4A5 while our cells are in the HUD plane66    .equ BKEY,0xFFE8F0                 | 3 words: last fill+colour per bar (only changes are composed)67    .ifndef LEGACY_HARD_CROWD68    .equ LEGACY_HARD_CROWD,069    .endif70    .text71    .globl _start72_start:73    bra.w frame74    bra.w load75    bra.w defaults76    bra.w roster77    bra.w gameend78    bra.w critroll79    bra.w critshow80    bra.w draw81    bra.w easylookup82    bra.w mkbar                        | +36 r38: compose a 32x8 bar into 4 tiles (shotmeter.s too)83    .if WITH_STARS_HUD84    bra.w clear_bars                   | +40 Native Stars entry cleanup before its window draws85    .endif8687| d0 = crit rating (0..100), a3 = shooter -> d1 = scaled rating, d0 = 100088| (replaces move.w d0,d1 / move.w #1000,d0 at $2FF462).89critroll:90    move.w %d0,%d191    cmpi.w #0x4352,MAG92    bne.s 9f93    move.w PCTH,%d094    btst #6,0x62(%a3)95    beq.s 1f96    move.w PCTA,%d0971:  .if WITH_MOMENTUM98    jsr 0x22601099    .endif100    muls.w %d1,%d0101    divs.w #100,%d0102    add.w %d0,%d1103    bpl.s 9f104    moveq #0,%d11059:  addi.w #30,%d1                     | r26 (jdc): +3 percentage points for every crit-eligible shot106    cmpi.w #2,DIFF                     | (rating 100: 13 %, 50: 8 %); Easy (r27): a human's slapshot107    bne.s 8f                           | always crits (RNG(1000) < 1000)108    btst #3,0x62(%a3)109    beq.s 8f110    move.w #1000,%d11118:  move.w #1000,%d0112    rts113114| +32 easylookup (r27): replaces bsr.w lookup / tst.w d0 at $2FF45A in critical.s. A human in115| Easy counts as rated even with Crit 0, so every forehand slapshot reaches the roll above.116easylookup:117    jsr 0x2FF40C                       | critical.s lookup: d0 = rating118    .if WITH_EASY_STATS119    cmpi.w #2,DIFF120    bne.s 1f121    jsr 0x2EE410122    .else123    cmpi.w #2,DIFF124    bne.s 1f125    btst #3,0x62(%a3)126    beq.s 1f127    moveq #1,%d0128    .endif1291:  tst.w %d0130    rts131132| d0 = crit rating for the Team Roster column, a2 = team block (replaces jmp $8E4C).133critshow:134    .if WITH_EASY_STATS135    cmpi.w #2,DIFF136    bne.s easy_crit_native137    jsr 0x2EE40C138    jmp 0x8E4C139easy_crit_native:140    .endif141    cmpi.w #0x4352,MAG142    bne.s 9f143    movem.l %d1-%d2,-(%sp)144    move.w PCTH,%d2145    cmpa.w #0xC6CE,%a2146    beq.s 1f147    move.w PCTA,%d2148    cmpa.w #0xCA32,%a2149    bne.s 2f1501:  .if WITH_MOMENTUM151    jsr 0x226014152    .endif153    move.w %d0,%d1154    muls.w %d2,%d1155    divs.w #100,%d1156    add.w %d1,%d0157    bpl.s 2f158    moveq #0,%d01592:  movem.l (%sp)+,%d1-%d21609:  jmp 0x8E4C161162| Roster during a game shows the rating with the team's current percent163| (Overall and 0..99 ratings, mode $0A; Handed/Weight/Energy/Fighting/Crit unchanged).164roster:165    jsr 0xFA9F8                        | d0 value, d1 limit, d4 = mode<<16 | mask166    .if WITH_EASY_STATS167    cmpi.w #2,DIFF168    bne.s easy_roster_native169    jmp 0x2EE408170easy_roster_native:171    .endif172    movem.l %d2-%d3,-(%sp)173    cmpi.w #0x4352,MAG174    bne.s 9f175    move.l %d4,%d2176    swap %d2177    cmpi.w #0x0A,%d2178    bne.s 9f179    move.w PCTH,%d2180    cmpa.w #0xC6CE,%a2181    beq.s 1f182    move.w PCTA,%d2183    cmpa.w #0xCA32,%a2184    bne.s 9f1851:  .if WITH_MOMENTUM186    jsr 0x226014187    .endif188    move.w %d0,%d3189    muls.w %d2,%d3190    divs.w #100,%d3191    add.w %d3,%d0192    bpl.s 2f193    moveq #0,%d01942:  cmp.w %d1,%d0195    blt.s 9f196    move.w %d1,%d0197    subq.w #1,%d01989:  movem.l (%sp)+,%d2-%d3199    rts200201gameend:                               | replaces bsr $169D0 / move.w #$2700,sr at $172FA202    .if WITH_GK_OFFENSE203    jsr 0x2DF410204    .else205    jsr 0x169D0206    .endif207    .if WITH_PASS_PLAN208    jsr 0x2DF808209    .endif210    clr.w MAG                          | next game (or Roster outside a game) starts neutral211    clr.l 0xFFF738                     | replay.s: no replay pending (ARST, ARCNT)212    clr.l 0xFFF73C213    clr.l 0xFFF630                     | trail.s HN/HT and TL: no stale afterglow from menu text214    clr.l 0xFFF634                     | (CNTDN, LASTG; r7: trail TL and GOK)215    clr.l 0xFFF640                     | shotmeter.s magic / shooter (r27)216    move.w #0x2700,%sr217    rts218219defaults:220    jsr 0x2FE200                       | fix 10 start-up defaults (replayed)221    clr.l DIFF                         | and mtap.s TM ($FFF07C, r16)222    .if WITH_PREF_DEFAULTS223    move.w #1,DIFF                    | requested default Hard; Easy remains2224    .endif225    jsr 0x2DE004                       | global automatic replay preference, default On226    rts227228| ---------------------------------------------------------------- loader tail229load:230    jsr 0x1FF002                       | replayed native/hack tail231    movem.l %d0-%d4/%a0-%a1,-(%sp)232    bsr.w valid233    move.l %a3,%d0234    sub.l #OBJ,%d0235    bcs.s 9f236    moveq #0x7F,%d1237    and.w %d0,%d1238    bne.s 9f239    lsr.l #7,%d0240    cmpi.l #12,%d0241    bcc.s 9f242    lea BASE,%a0243    move.w %d0,%d1244    lsl.w #4,%d1245    adda.w %d1,%a0246    lea 0x66(%a3),%a1247    moveq #15,%d12481:  move.b (%a1)+,(%a0)+249    dbra %d1,1b250    lea VAL,%a0251    st (%a0,%d0.w)252    lea APP,%a0253    move.b #0x7F,(%a0,%d0.w)254    .if WITH_EASY_STATS255    cmpi.w #2,DIFF256    bne.s load_native_scale257    jsr 0x2EE404258    bra.s 9f259load_native_scale:260    .endif261    bsr.w apply12629:  movem.l (%sp)+,%d0-%d4/%a0-%a1263    rts264265| ------------------------------------------------------------- once per frame266frame:267    movem.l %d0-%d5/%a0-%a4,-(%sp)268    movea.w #0xC018,%a6269    moveq #1,%d6270    bsr.w valid271    .if WITH_MOMENTUM272    cmpi.w #1,0xFFFFF84A273    bne.s momentum_enabled274    jsr 0x22600C275    cmpi.w #2,DIFF276    beq.w 5f                       | independent fixed Easy skills/energy277    bra.w 6f                       | native cache restores all active base ratings278momentum_enabled:279    .endif280    bsr.w events281    bsr.w decay282    move.w MOM,%d0283    .if LEGACY_HARD_CROWD284    tst.w DIFF285    bne.s 5f286    .else287    cmpi.w #2,DIFF                     | only Easy keeps fixed meters; Hard shares Normal288    beq.s 5f289    .endif290    move.w %d0,SIDEH                   | Normal: one shared momentum291    neg.w %d0292    move.w %d0,SIDEA293    move.w MOM,%d0294    ext.l %d0295    divs.w #100,%d0296    move.w %d0,PCTH297    neg.w %d0298    move.w %d0,PCTA299    bra.s 6f3005:  .if WITH_EASY_STATS301    jsr 0x2EE400302    bra.w 8f303    .else304    bsr.w humans                       | Hard: CPU full, human empty305    move.w #1000,%d0306    moveq #10,%d2307    tst.b HUM308    beq.s 4f309    neg.w %d0310    neg.w %d23114:  move.w %d0,SIDEH312    move.w %d2,PCTH313    move.w #1000,%d0314    moveq #10,%d2315    tst.b HUM+1316    beq.s 3f317    neg.w %d0318    neg.w %d23193:  move.w %d0,SIDEA320    move.w %d2,PCTA321    cmpi.w #2,DIFF                     | Easy (jdc r27): the mirror - humans full (+10 %), CPU empty322    bne.s 6f323    neg.w SIDEH324    neg.w PCTH325    neg.w SIDEA326    neg.w PCTA327    .endif3286:  move.w PCTH,%d0                    | rescale only when a percent changed (the loader329    move.w PCTA,%d1                    | hook applies it to new players itself)330    cmp.w LPH,%d0331    bne.s 7f332    cmp.w LPA,%d1333    beq.s 8f3347:  move.w %d0,LPH335    move.w %d1,LPA336    moveq #0,%d0337    movea.w #0xB04A,%a33381:  bsr.w apply1339    adda.w #0x80,%a3340    addq.w #1,%d0341    cmpi.w #12,%d0342    bcs.s 1b3438:3449:  jsr 0x2E4004                       | trail.s: crit flames first in the list, on top of the345    jsr 0x2E5000                       | goalie (only during a crit shot in play: few sprites); replay.s goal watch346    jsr 0x2ED000                       | shotmeter.s: charge / shot speed (r27)347    movem.l (%sp)+,%d0-%d5/%a0-%a4348    rts349350| +28 draw (2026-10-26-r7): the bars at the END of the sprite list, from trail.s's hook at351| $15F06 (a6/d6 = the native list). Drawn first (r24) they pushed the game's own sprites352| past the list's end $C2E8 in the ~90-sprite game intro, over $C2EC (pause flag):353| "Resume Game" froze before the faceoff. sprs stops at 80 sprites ($C298).354draw:                                  | A5 is the native DMA queue355    .if WITH_REFINEMENTS356    jmp 0x259000357clear_bars:358    jmp 0x259004359    .else360    btst #7,0xFFC2EC361    bne.w foreign_view362    .if WITH_MOMENTUM363    cmpi.w #1,0xFFFFF84A364    beq.w clear_bars365    .endif366    bsr.w inreplay                     | no meters during the automatic replay (r23)367    bcs.w 8f368    movem.l %d0-%d6/%a0-%a1,-(%sp)369    cmpi.w #0xB4A5,BSHOWN              | first frame (game start, after a replay): compose both370    beq.s 1f371    move.l #-1,BKEY3721:  moveq #0,%d6                       | bit0/1: a bar changed373    move.w SIDEA,%d0374    lea BBUF,%a0375    lea BKEY,%a1376    bsr.w crowdbar                     | d4 = palette bits377    move.w %d4,%d5378    move.w SIDEH,%d0379    lea BBUF+128,%a0380    lea BKEY+2,%a1381    bsr.w crowdbar382    tst.w %d6383    beq.s 2f384    move.l #BBUF,(%a5)+                | 8 tiles, only when one changed385    move.w #128,(%a5)+386    move.w #BTILE*32,(%a5)+3872:  move.w %sr,-(%sp)388    ori.w #0x700,%sr389    lea 0xC00000,%a1390    move.w #0x8F02,0xC00004            | auto-increment 2391    move.l #0x60000003+((24*64+27)*2<<16),0xC00004392    move.w #0x8000+BTILE,%d0393    or.w %d5,%d0394    bsr.w cells4395    move.l #0x60000003+((26*64+27)*2<<16),0xC00004396    move.w #0x8000+BTILE+4,%d0397    or.w %d4,%d0398    bsr.w cells4399    move.w (%sp)+,%sr400    move.w #0xB4A5,BSHOWN401    movem.l (%sp)+,%d0-%d6/%a0-%a1402    rts403foreign_view:                          | horizontal penalty/ticker/menus own these map cells404    clr.w BSHOWN                       | do not erase the native overlay after it drew405    rts406clear_bars:4078:  cmpi.w #0xB4A5,BSHOWN              | replay / other view: our cells go, once408    bne.s 9f409    clr.w BSHOWN410    movem.l %d0/%a1,-(%sp)411    move.w %sr,-(%sp)412    ori.w #0x700,%sr413    lea 0xC00000,%a1414    move.w #0x07FF,%d0415    move.w #0x8F02,0xC00004416    move.l #0x60000003+((24*64+27)*2<<16),0xC00004417    move.w %d0,(%a1)418    move.w %d0,(%a1)419    move.w %d0,(%a1)420    move.w %d0,(%a1)421    move.l #0x60000003+((26*64+27)*2<<16),0xC00004422    move.w %d0,(%a1)423    move.w %d0,(%a1)424    move.w %d0,(%a1)425    move.w %d0,(%a1)426    move.w (%sp)+,%sr427    movem.l (%sp)+,%d0/%a14289:  rts429cells4:                                | d0 = first map word -> four consecutive tiles430    move.w %d0,(%a1)431    addq.w #1,%d0432    move.w %d0,(%a1)433    addq.w #1,%d0434    move.w %d0,(%a1)435    addq.w #1,%d0436    move.w %d0,(%a1)437    rts438crowdbar:                              | d0 = side -1000..1000, a0 = 128 bytes, a1 = its key439    move.w %d0,%d4                     | -> d4 palette bits, d6 nonzero if composed440    add.w #1000,%d0441    mulu.w #3,%d0442    divu.w #200,%d0                    | 0..30 px443    moveq #1,%d1                       | white on black (palette 0)444    moveq #4,%d2445    tst.w %d4446    bpl.s 1f447    moveq #0xA,%d1                     | red on black (palette 1: A red, 7 black, 1 light grey)448    moveq #7,%d24491:  moveq #1,%d3                       | neutral tick at x 16450    move.w %d1,%d4451    lsl.w #8,%d4452    or.w %d0,%d4453    cmp.w (%a1),%d4454    beq.s 3f455    move.w %d4,(%a1)456    moveq #1,%d6457    bsr.s mkbar4583:459    moveq #0,%d4460    cmpi.w #0xA,%d1461    bne.s 2f462    move.w #0x2000,%d44632:  rts464465    .endif466467| mkbar: d0 = fill 0..30 px (x 1..fill), d1 = fill colour, d2 = background colour, d3 bit0 = tick468| (x 16 background in rows 1-6, colour 1 in rows 0/7), bit1 = rows 0/7 solid background (the shot469| bar inside its black panel: no see-through lines, r39), a0 = 4 tiles (128 bytes).470| Rows 0/7 transparent, 1/6 background, 2-5 fill - the old sprite bar exactly.471mkbar:472    movem.l %d0-%d7/%a0-%a1,-(%sp)473    moveq #0,%d6474    move.w %d2,%d6475    mulu.w #0x1111,%d6476    move.w %d6,%d7477    swap %d6478    move.w %d7,%d6479    movea.l %d6,%a1                    | background row480    moveq #0,%d5                       | tile x base4811:  moveq #0,%d4482    moveq #7,%d7483    move.w %d5,%d64842:  lsl.l #4,%d4485    cmpi.w #1,%d6486    blt.s 4f487    cmp.w %d0,%d6488    bgt.s 4f489    btst #0,%d3490    beq.s 3f491    cmpi.w #16,%d6492    beq.s 4f4933:  or.b %d1,%d4494    bra.s 5f4954:  or.b %d2,%d44965:  addq.w #1,%d6497    dbra %d7,2b498    moveq #0,%d7                       | rows 0/7499    btst #1,%d3500    beq.s 61f501    move.l %a1,%d7                     | solid: background colour502    bra.s 6f50361: btst #0,%d3504    beq.s 6f505    cmpi.w #16,%d5506    bne.s 6f507    move.l #0x10000000,%d75086:  move.l %d7,(%a0)+509    move.l %a1,(%a0)+510    move.l %d4,(%a0)+511    move.l %d4,(%a0)+512    move.l %d4,(%a0)+513    move.l %d4,(%a0)+514    move.l %a1,(%a0)+515    move.l %d7,(%a0)+516    addq.w #8,%d5517    cmpi.w #32,%d5518    blt.s 1b519    movem.l (%sp)+,%d0-%d7/%a0-%a1520    rts521522inreplay:                              | C set while replay.s plays its automatic replay ($A502..$A506)523    move.l %d0,-(%sp)524    move.w 0xFFF738,%d0525    subi.w #0xA502,%d0526    .if WITH_STARS_HUD527    cmpi.w #6,%d0                      | A502..A506 replay, A507 native postgame HUD ownership528    .else529    cmpi.w #5,%d0                      | 0..4 -> C (unsigned below 5)530    .endif531    movem.l (%sp)+,%d0                 | (movem keeps the flags)532    rts533534valid:535    cmpi.w #0x4352,MAG536    beq.s 9f537    move.w #0x4352,MAG538    clr.w MOM539    clr.w PCTH540    clr.w PCTA541    clr.w SIDEH542    clr.w SIDEA543    clr.w HUM544    clr.w LPH545    clr.w LPA546    clr.w TICK547    clr.w 0xFFF736                     | onetimer PEND and replay ARST/ARCNT/CNTDN/LASTG start clean in a game548    clr.l 0xFFF738                     | (menus write text over $FFF7xx; 2026-10-26-r3)549    clr.l 0xFFF73C550    clr.l 0xFFF630                     | r37: trail.s HN/HT and TL/GOK start clean too (season menu551    clr.l 0xFFF634                     | text left HT 3348: no flame tiles for minutes of crits)552    lea APP,%a0553    moveq #23,%d15541:  clr.b (%a0)+                        | APP and VAL555    dbra %d1,1b556    bsr.w snap557    move.w 0xFFC468,SEC558    jsr 0x2EB034                       | r36: seasonplayers.s keeps the season's Difficulty5599:  rts560561snap:                                  | LAST = current team counters562    move.w HOME,LAST563    move.w HOME+0x10,LAST+2564    move.w AWAY,LAST+4565    move.w AWAY+0x10,LAST+6566    rts567568events:569    move.w HOME,%d0570    add.w HOME+0x10,%d0571    add.w AWAY,%d0572    add.w AWAY+0x10,%d0573    move.w LAST,%d1574    add.w LAST+2,%d1575    add.w LAST+4,%d1576    add.w LAST+6,%d1577    cmp.w %d1,%d0578    .if LEGACY_WHISTLE_MOMENTUM579    bcc.s 1f580    .else581    beq.s 1f                          | unchanged counters: same taken-branch cost as r7582    bcc.w changed_events583    .endif584    clr.w MOM                          | counters went down: a new game585    clr.w HUM586    bra.w snap5871:588credit_events:589    move.w MOM,%d3590    move.w HOME,%d0591    sub.w LAST,%d0592    mulu.w #200,%d0593    add.w %d0,%d3594    move.w HOME+0x10,%d0595    sub.w LAST+2,%d0596    mulu.w #150,%d0597    add.w %d0,%d3598    move.w AWAY,%d0599    sub.w LAST+4,%d0600    mulu.w #200,%d0601    sub.w %d0,%d3602    move.w AWAY+0x10,%d0603    sub.w LAST+6,%d0604    mulu.w #150,%d0605    sub.w %d0,%d3606    cmpi.w #1000,%d3607    ble.s 2f608    move.w #1000,%d36092:  cmpi.w #-1000,%d3610    bge.s 3f611    move.w #-1000,%d36123:  move.w %d3,MOM613    bra.w snap614615humans:                                | remember which side has a human-controlled object616    movea.w #0xB04A,%a0617    moveq #11,%d16181:  btst #3,0x62(%a0)619    beq.s 2f620    lea HUM,%a1621    btst #6,0x62(%a0)622    beq.s 3f623    addq.w #1,%a16243:  st (%a1)6252:  adda.w #0x80,%a0626    dbra %d1,1b627    rts628629decay:630    move.w 0xFFC468,%d0631    cmp.w SEC,%d0632    beq.s 9f633    move.w %d0,SEC634    move.w MOM,%d0635    ext.l %d0636    divs.w #128,%d0637    bne.s 1f638    moveq #1,%d0                       | at least one step toward zero639    tst.w MOM640    bgt.s 1f641    beq.s 9f642    moveq #-1,%d06431:  sub.w %d0,MOM6449:  rts645646| a3 object, d0 index 0..11: write base*(100+p)/100 when p changed647apply1:648    movem.l %d0-%d4/%a0-%a2,-(%sp)649    lea VAL,%a0650    tst.b (%a0,%d0.w)651    beq.s 9f652    move.w PCTH,%d4653    btst #6,0x62(%a3)                  | $CA32 team (native stats block select)654    beq.s 1f655    move.w PCTA,%d46561:  .if WITH_MOMENTUM657    jsr 0x226018658    .endif659    lea APP,%a0660    cmp.b (%a0,%d0.w),%d4661    beq.s 9f662    move.b %d4,(%a0,%d0.w)663    lea BASE,%a1664    lsl.w #4,%d0665    adda.w %d0,%a1666    lea offs(%pc),%a26672:  move.b (%a2)+,%d0                  | offset from +$66, bit 6 = lower is better668    bmi.s 9f669    move.w %d4,%d3670    bclr #6,%d0671    beq.s 3f672    neg.w %d36733:  ext.w %d0674    moveq #0,%d1675    move.b (%a1,%d0.w),%d1676    move.w %d1,%d2677    muls.w %d3,%d2678    divs.w #100,%d2679    add.w %d1,%d2680    bpl.s 4f681    moveq #0,%d26824:  moveq #30,%d3                     | ceiling 30, or the base if it is higher683    cmp.w %d1,%d3684    bge.s 6f685    move.w %d1,%d36866:  cmp.w %d3,%d2687    ble.s 5f688    move.w %d3,%d26895:  move.b %d2,0x66(%a3,%d0.w)690    bra.s 2b6919:  movem.l (%sp)+,%d0-%d4/%a0-%a2692    rts693| ratings: $68..$6D, $6E, $70..$73, $75 ($6A/$6B are inverted timings); keep694| roster slot $66, weight $67, jersey $6F and fighting $74 in legacy scaling.695offs: .byte 0x02,0x03,0x44,0x45,0x06,0x07,0x08,0x0A,0x0B,0x0C,0x0D,0x0F,0xFF696    .even697698| d0 = side value (-1000..1000), d2 = y. Sprites: tick, fill, partial, background.699700    .if !LEGACY_WHISTLE_MOMENTUM701| Only changed counters reach this guard. Consume blocked events immediately,702| leaving earned M intact and preventing a deferred award on the next faceoff.703changed_events:704    move.b 0xFFFFC2EA,%d0705    andi.b #0x51,%d0                  | clock stopped / highlight / game over706    bne.w snap707    move.b 0xFFFFC2EC,%d0708    andi.b #0x81,%d0                  | pause or foreign view709    bne.w snap710    btst #2,0xFFFFC2F2                | faceoff setup711    bne.w snap712    move.w 0xFFF738,%d0713    subi.w #0xA502,%d0714    cmpi.w #6,%d0                     | replay or Stars overlay715    bcs.w snap716| Live deltas combine the two sides before multiplication. Word arithmetic is717| identical, including wrapped deltas; absolute-short RAM operands save cycles.718| Together with the fixed padding this preserves r7 live-event execution time.719    move.w 0xFFFFF702,%d3720    move.w 0xFFFFC6CE,%d0721    sub.w 0xFFFFF704,%d0722    sub.w 0xFFFFCA32,%d0723    add.w 0xFFFFF708,%d0724    mulu.w #200,%d0725    add.w %d0,%d3726    move.w 0xFFFFC6DE,%d0727    sub.w 0xFFFFF706,%d0728    sub.w 0xFFFFCA42,%d0729    add.w 0xFFFFF70A,%d0730    mulu.w #150,%d0731    add.w %d0,%d3732    cmpi.w #1000,%d3733    ble.s live_low734    move.w #1000,%d3735live_low:736    cmpi.w #-1000,%d3737    bge.s live_store738    move.w #-1000,%d3739live_store:740    move.w %d3,0xFFFFF702741    .rept 11742    nop743    .endr744    move.w 0xFFFFC6CE,0xFFFFF704745    move.w 0xFFFFC6DE,0xFFFFF706746    move.w 0xFFFFCA32,0xFFFFF708747    move.w 0xFFFFCA42,0xFFFFF70A748    rts749    .endif750