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