-
Notifications
You must be signed in to change notification settings - Fork 0
/
mm4.asm
2566 lines (2277 loc) · 110 KB
/
mm4.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
.inesprg 32 ; 32x 16KB PRG code (64 banks of 8KB)
.ineschr 0 ; 0x 8KB CHR data
.inesmap 4 ; mapper 4 = MMC3, 8KB PRG, 1/2KB CHR bank swapping
.inesmir 0 ; background mirroring
; Handy pseudo instructions... only make sense in the context of CMPing a number...
BLT .macro
BCC \1 ; A < CMP (unsigned)
.endm
BGE .macro
BCS \1 ; A >= CMP (unsigned)
.endm
BLS .macro
BMI \1 ; A < CMP (signed)
.endm
BGS .macro
BPL \1 ; A >= CMP (signed)
.endm
; Clarifying pseudo instructions
ADD .macro ; RegEx S&R "CLC.*\n.*?ADC" -> "ADD"
CLC
ADC \1
.endm
SUB .macro ; RegEx S&R "SEC.*\n.*?SBC" -> "SUB"
SEC
SBC \1
.endm
NEG .macro ; RegEx S&R "EOR #\$ff.*\n.*ADD #\$01" -> "NEG"
EOR #$ff
ADD #$01
.endm
; This is used in video update streams; since the video address register
; takes the address high-then-low (contrary to 6502's normal low-then-high),
; this allows a 16-bit value but "corrects" it to the proper endianness.
vaddr .macro
.byte (\1 & $FF00) >> 8
.byte (\1 & $00FF)
.endm
; Pads bytes to align to nearest 64 byte boundary for DMC samples
; SB: This would be useful for your own works, but I can't use
; it in the natively disassembly since the assembler pads zeroes
; instead of $FF values... just FYI!
;
; Usage example:
;
; .LabelPriorToDMC: DMCAlign .LabelPriorToDMC
DMCAlign: .macro
.ds ((\1 + $3F) & $FFC0) - \1
.endm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; PPU I/O regs (CPU side)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; PPU_CTL1:
; 0-1: Name table address, changes between the four name tables at $2000 (0), $2400 (1), $2800 (2) and $2C00 (3).
; 2: Clear, PPU incs by 1 ("horizontal"); Set, PPU incs by 32 ("vertical")
; 3: Which pattern table holds for sprites; 0 for PT1 ($0000) or 1 for PT2 ($1000)
; 4: Which pattern table holds for BG; 0 for PT1 ($0000) or 1 for PT2 ($1000)
; 5: Set to use 8x16 sprites instead of 8x8
; 7: Set to generate VBlank NMIs
PPU_CTL1 = $2000 ; Write only
; PPU_CTL2:
; 0: Clear for color, set for mono
; 1: Clear to clip 8 left pixels of BG
; 2: Clear to clip 8 left pixels of sprites
; 3: If clear, BG hidden
; 4: If clear, sprites hidden
; 5-7: BG color in mono mode, "color intensity" in color mode (??)
PPU_CTL2 = $2001 ; Write only
; PPU_STAT:
; 4: if set, can write to VRAM, else writes ignored
; 5: if set, sprite overflow occurred on scanline
; 6: Set if any non-transparent pixel of sprite 0 is overlapping a non-transparent pixel of BG
; 7: VBlank is occurring (cleared after read)
PPU_STAT = $2002
; Sprites: 256 bytes, each sprite takes 4, so 64 sprites total
; Only 8 sprites per scanline, sprite 0 is drawn on top (thus highest priority)
; PPU_SPR_ADDR / PPU_SPR_DATA
; * Byte 0 - Stores the y-coordinate of the top left of the sprite minus 1.
; * Byte 1 - Index number of the sprite in the pattern tables.
; * Byte 2 - Stores the attributes of the sprite.
; * Bits 0-1 - Most significant two bits of the colour. (Or "palette" 0-3)
; * Bit 5 - Indicates whether this sprite has priority over the background.
; * Bit 6 - Indicates whether to flip the sprite horizontally.
; * Bit 7 - Indicates whether to flip the sprite vertically.
; * Byte 3 - X coordinate
PPU_SPR_ADDR = $2003 ; Set address sprite data
PPU_SPR_DATA = $2004 ; Read or write this sprite byte
PPU_SCROLL = $2005 ; Scroll register; read PPU_STAT, then write horiz/vert scroll
PPU_VRAM_ADDR = $2006 ; VRAM address (first write is high, next write is low)
PPU_VRAM_DATA = $2007 ; Data to read/write at this address
; Note that all transparent colors ($3F04, $3F08, $3F0C, $3F10, $3F14, $3F18 and $3F1C) are mirrored from 3F00
PPU_BG_PAL = $3F00 ; 3F00-3F0F
PPU_SPR_PAL = $3F10 ; 3F10-3F1F
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SOUND I/O regs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; $4000(rct1)/$4004(rct2)/$400C(noise) bits
; ---------------------------------------
; 0-3 volume / envelope decay rate
; 4 envelope decay disable
; 5 length counter clock disable / envelope decay looping enable
; 6-7 duty cycle type (unused on noise channel)
; Duty cycles:
; 00 = a weak, grainy tone. (12.5% Duty), 01 = a solid mid-strength tone. (25% Duty),
; 10 = a strong, full tone (50% Duty), 11 = sounds a lot like 01 (25% Duty negated)
PAPU_CTL1 = $4000 ; pAPU Pulse 1 Control Register.
PAPU_CTL2 = $4004 ; pAPU Pulse 2 Control Register.
PAPU_NCTL1 = $400C ; pAPU Noise Control Register 1.
; $4008(tri) bits
; ---------------
; 0-6 linear counter load register
; 7 length counter clock disable / linear counter start
PAPU_TCR1 = $4008 ; pAPU Triangle Control Register 1.
; $4001(rct1)/$4005(rct2) bits
; --------------------------
; 0-2 right shift amount
; 3 decrease / increase (1/0) wavelength
; 4-6 sweep update rate
; 7 sweep enable
PAPU_RAMP1 = $4001 ; pAPU Pulse 1 Ramp Control Register.
PAPU_RAMP2 = $4005 ; pAPU Pulse 2 Ramp Control Register.
; $4002(rct1)/$4006(rct2)/$400A(Tri) bits
; -------------------------------------
; 0-7 8 LSB of wavelength
PAPU_FT1 = $4002 ; pAPU Pulse 1 Fine Tune (FT) Register.
PAPU_FT2 = $4006 ; pAPU Pulse 2 Fine Tune (FT) Register.
PAPU_TFREQ1 = $400A ; pAPU Triangle Frequency Register 1.
; $400E(noise) bits
; -----------------
; 0-3 playback sample rate
; 4-6 unused
; 7 random number type generation
PAPU_NFREQ1 = $400E ; pAPU Noise Frequency Register 1.
; $4003(rct1)/$4007(rct2)/$400B(tri)/$400F(noise) bits
; --------------------------------------------------
; 0-2 3 MS bits of wavelength (unused on noise channel) (the "high" frequency)
; 3-7 length of tone
PAPU_CT1 = $4003 ; pAPU Pulse 1 Coarse Tune (CT) Register.
PAPU_CT2 = $4007 ; pAPU Pulse 2 Coarse Tune (CT) Register.
PAPU_TFREQ2 = $400B ; pAPU Triangle Frequency Register 2.
PAPU_NFREQ2 = $400F ; pAPU Noise Frequency Register 2.
; $4010 - DMC Play mode and DMA frequency
; Bits 0-3:
; f period
; ----------
; 0 $1AC
; 1 $17C
; 2 $154
; 3 $140
; 4 $11E
; 5 $0FE
; 6 $0E2
; 7 $0D6
; 8 $0BE
; 9 $0A0
; A $08E
; B $080
; C $06A
; D $054
; E $048
; F $036
; Bits 6-7: this is the playback mode.
; 00 - play DMC sample until length counter reaches 0 (see $4013)
; x1 - loop the DMC sample (x = immaterial)
; 10 - play DMC sample until length counter reaches 0, then generate a CPU
PAPU_MODCTL = $4010 ; pAPU Delta Modulation Control Register.
PAPU_MODDA = $4011 ; pAPU Delta Modulation D/A Register.
PAPU_MODADDR = $4012 ; pAPU Delta Modulation Address Register.
PAPU_MODLEN = $4013 ; pAPU Delta Modulation Data Length Register.
; read
; ----
; 0 rectangle wave channel 1 length counter status
; 1 rectangle wave channel 2 length counter status
; 2 triangle wave channel length counter status
; 3 noise channel length counter status
; 4 DMC is currently enabled (playing a stream of samples)
; 5 unknown
; 6 frame IRQ status (active when set)
; 7 DMC's IRQ status (active when set)
;
; write
; -----
; 0 rectangle wave channel 1 enable
; 1 rectangle wave channel 2 enable
; 2 triangle wave channel enable
; 3 noise channel enable
; 4 enable/disable DMC (1=start/continue playing a sample;0=stop playing)
; 5-7 unknown
PAPU_EN = $4015 ; R/W pAPU Sound Enable
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OTHER I/O regs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPR_DMA = $4014 ; Sprite DMA Register -- DMA from CPU memory at $100 x n to SPR-RAM (256 bytes)
; Read / Write Joypad 1/2:
; * Bit 0 - Reads data from joypad or causes joypad strobe
; when writing.
; * Bit 3 - Indicates whether Zapper is pointing at a sprite.
; * Bit 4 - Cleared when Zapper trigger is released.
; Only bit 0 is involved in writing.
JOYPAD = $4016
; Frame counter control
; Changes the frame counter that changes updates on sound; any write resets
; the frame counter, good for synchronizing sound with VBlank etc.
; 0 4, 0,1,2,3, 0,1,2,3,..., etc.
; 1 0,1,2,3,4, 0,1,2,3,4,..., etc.
; bit 6 - enable frame IRQs (when zero)
; bit 7 - 0 = 60 IRQs a frame / 1 = 48 IRQs a frame (obviously need bit 6 clear to use)
; Interestingly, both of the above are clear on bootup, meaning IRQs are being generated,
; but the 6502 ignores NMIs on startup; also, need to read from $4015 (PAPU_EN) to acknowledge
; the interrupt, otherwise it holds the status on!
FRAMECTR_CTL = $4017
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MMC3 regs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MMC3_COMMAND:
; Bits 0-2 - Command number:
; * 0 - Swap two 1 KB VROM banks at PPU $0000.
; * 1 - Swap two 1 KB VROM banks at PPU $0800.
; * 2 - Swap one 1 KB VROM bank at PPU $1000.
; * 3 - Swap one 1 KB VROM bank at PPU $1400.
; * 4 - Swap one 1 KB VROM bank at PPU $1800.
; * 5 - Swap one 1 KB VROM bank at PPU $1C00.
; * 6 - Swap PRG-ROM bank at either $8000 or $C000 based on bit 6.
; * 7 - Swap PRG-ROM bank at either $A000
;
; Bit 6 - If 0, enables swapping at $8000 and $A000, otherwise enables
; swapping at $C000 and $A000.
;
; Bit 7 - If 1, causes addresses for commands 0-5 to be the exclusive-or
; of the address stated and $1000.
; Note that bit 6 is clear on all of these consistently since MM4 uses the PRG switch this way
MMC3_2K_TO_PPU_0000 = %00000000 ; 0
MMC3_2K_TO_PPU_0800 = %00000001 ; 1
MMC3_1K_TO_PPU_1000 = %00000010 ; 2
MMC3_1K_TO_PPU_1400 = %00000011 ; 3
MMC3_1K_TO_PPU_1800 = %00000100 ; 4
MMC3_1K_TO_PPU_1C00 = %00000101 ; 5
MMC3_8K_TO_PRG_8000 = %00000110 ; 6
MMC3_8K_TO_PRG_A000 = %00000111 ; 7
MMC3_PPU_XOR_1000 = %10000000
MMC3_COMMAND = $8000 ; consult ref
MMC3_PAGE = $8001 ; page number to MMC3_COMMAND
MMC3_MIRROR = $A000 ; bit 0 clear is horizontal mirroring, bit 0 set is vertical mirroring
MMC3_SRAM_EN = $A001 ; bit 7 set to enable SRAM at $6000-$7FFF
MMC3_IRQCNT = $C000 ; Countdown to an IRQ
MMC3_IRQLATCH = $C001 ; Store a temp val to be copied to MMC3_IRQCNT later
MMC3_IRQDISABLE = $E000 ; Disables IRQ generation and copies MMC3_IRQLATCH to MMC3_IRQCNT
MMC3_IRQENABLE = $E001 ; Enables IRQ generation
.data ; Using .data instead of .zp to export labels
.org $0000
; AUTOGENERATED: Remember the "ds" ranges may be too large, but they shouldn't be too small at least
Temp_Var0: .ds 1 ; $0000
Temp_Var1: .ds 1 ; $0001
Temp_Var2: .ds 1 ; $0002
Temp_Var3: .ds 1 ; $0003
Temp_Var4: .ds 1 ; $0004
Temp_Var5: .ds 1 ; $0005
Temp_Var6: .ds 1 ; $0006
Temp_Var7: .ds 1 ; $0007
Temp_Var8: .ds 1 ; $0008
Temp_Var9: .ds 1 ; $0009
Temp_Var10: .ds 1 ; $000A
Temp_Var11: .ds 1 ; $000B
Temp_Var12: .ds 1 ; $000C
Temp_Var13: .ds 1 ; $000D
Temp_Var14: .ds 1 ; $000E
Temp_Var15: .ds 1 ; $000F
Temp_Var16: .ds 1 ; $0010
Temp_Var17: .ds 1 ; $0011
Temp_Var18: .ds 1 ; $0012
Temp_Var19: .ds 1 ; $0013
PAD_A = $80
PAD_B = $40
PAD_SELECT = $20
PAD_START = $10
PAD_UP = $08
PAD_DOWN = $04
PAD_LEFT = $02
PAD_RIGHT = $01
Ctlr1_Pressed: .ds 1 ; $0014
Ctlr2_Pressed: .ds 1 ; $0015
Ctlr1_Held: .ds 1 ; $0016
Ctlr2_Held: .ds 1
CommitPal_Flag: .ds 1 ; If set non-zero, commit palette (PalData_1 -> PPU)
CommitGBuf_Flag: .ds 1 ; If set non-zero, commit graphics buffer (horizontal increment)
CommitGBuf_FlagV: .ds 1 ; If set non-zero, commit graphics buffer (vertical increment)
; CommitBG_Flag:
; Bit 6 ($40) set ONLY - Commit BG attribute data stored in all 64 bytes of Level_ScreenTileModData
; Bit 7 ($80) set - Commits BG to the "other" side of the BG mirror (for large bosses); lower 7 bits specify MetaBlk_Index (generally $00?); paired with CommitBG_ScrSel which specifies the screen
CommitBG_Flag: .ds 1 ; $001B
CommitBG_ScrSel: .ds 1 ; See CommitBG_Flag; specifies the screen
RAM_001D: .ds 1 ; $001D
RAM_001E: .ds 1 ; $001E
; Player_Midpoint:
; 0 - Start at beginning of level
; 1 - Start at midpoint of level
; 2 - Start inside boss gates
Player_Midpoint: .ds 1
; Pointer to current metablock screen layout data
MetaBlk_ScrAddrL: .ds 1
MetaBlk_ScrAddrH: .ds 1
; TileMap_Index: OR'd with 32 to make a page for $A000
TMAP_BRIGHTMAN = $00 ; $00 - Bright Man
TMAP_TOADMAN = $01 ; $01 - Toad Man
TMAP_DRILLMAN = $02 ; $02 - Drill Man
TMAP_PHARAOHMAN = $03 ; $03 - Pharaoh Man
TMAP_RINGMAN = $04 ; $04 - Ring Man
TMAP_DUSTMAN = $05 ; $05 - Dust Man
TMAP_DIVEMAN = $06 ; $06 - Dive Man
TMAP_SKULLMAN = $07 ; $07 - Skull Man
TMAP_COSSACK1 = $08 ; $08 - Cossack 1
TMAP_COSSACK2 = $09 ; $09 - Cossack 2
TMAP_COSSACK3 = $0A ; $0A - Cossack 3
TMAP_COSSACK4 = $0B ; $0B - Cossack 4
TMAP_WILY1 = $0C ; $0C - Wily 1
TMAP_WILY2 = $0D ; $0D - Wily 2
TMAP_WILY3 = $0E ; $0E - Wily 3
TMAP_WILY4 = $0F ; $0F - Wily 4
TMAP_CREDITLOGO = $10 ; $10 - startup credit, logo, stage select, in-game menu
TMAP_TITLE = $11 ; $11 - title
TMAP_COSSACKINTRO = $12 ; $12 - Cossack fortress intro
TMAP_INTROSTORY = $13 ; $13 - intro story
TMAP_ENDING = $14 ; $14 - Ending
TileMap_Index: .ds 1 ; $0022
RAM_0023: .ds 1 ; $0023
ScreenUpd_CurCol: .ds 1 ; wraps 0-31, column on screen to draw
RAM_0025: .ds 1 ; $0025
RAM_0026: .ds 1 ; $0026
RAM_0027: .ds 1 ; $0027
RAM_0028: .ds 1 ; $0028
MetaBlk_Index: .ds 1 ; Index of current metablock to select the 16x16 tile
MetaBlk_CurScreen: .ds 1 ; Current "screen" to offset to in metablock data (e.g. passed as 'Y' into PRG062_SetMetaBlkAddr)
; "Segment" increases per ladder climb, screen transition, etc., see e.g. PRG062_CC79
; Level_SegCurData: Not fully known yet but...
; Lower nibble holds index to Level_SegmentDefs (current segment)
; Upper nibble set by Level_SegmentDefs, indexed by the lower nibble; see following defs
SEGDIR_BOSSGATE = $10 ; Boss gate is the next segment to the right (combine with SEGDIR_HORIZONTAL)
SEGDIR_HORIZONTAL = $20 ; Segment horizontal scrolling (typical)
SEGDIR_DOWN = $40 ; Segment allows downward transit transition
SEGDIR_UP = $80 ; Segment allows upward transit transition
Level_SegCurData: .ds 1 ; $002B
.ds 1 ; Unused
; Level_SegTotalRelScreen / Level_SegCurrentRelScreen
; Total screens for current segment (before a transition happens) and current
; relative screen you're on (towards the total); once the final screen is
; reached, horizontal scrolling (if applicable) stops...
Level_SegTotalRelScreen: .ds 1
Level_SegCurrentRelScreen: .ds 1
Level_LastScrollDir: .ds 1 ; 1 for right / up, 2 for left / down
; Player_State:
; NOTE: A lot of game logic checks if Player_State >= PLAYERSTATE_HURT and
; decides in general not to damage Player if that case is true.
PLAYERSTATE_STAND = 0 ; Player standing on ground
PLAYERSTATE_FALLJUMP = 1 ; Player falling/jumping
PLAYERSTATE_SLIDING = 2 ; Player sliding on ground
PLAYERSTATE_CLIMBING = 3 ; Player is climbing on ladder
PLAYERSTATE_RUSHMARINE = 4 ; Player is riding in Rush Marine
PLAYERSTATE_WIREADAPTER = 5 ; Player is using Wire Adapter
PLAYERSTATE_HURT = 6 ; Player hurt
PLAYERSTATE_DEAD = 7 ; Player is dead
PLAYERSTATE_TELEPORTOUT = 8 ; Player teleport out, end level
PLAYERSTATE_BOSSWAIT = 9 ; Player is waiting for boss to do intro / fill energy
PLAYERSTATE_READY = 10 ; "READY" opening animation, teleport in
PLAYERSTATE_ENDLEVEL = 11 ; Player runs to center to gain power (unless TileMap_Index >= 8), and then leads to PLAYERSTATE_TELEPORTOUT
PLAYERSTATE_COSSACKGRAB = 12 ; Player grabbed by Cossack boss claw grip
PLAYERSTATE_POSTCOSSACK = 13 ; Afted defeating Cossack, Player marches to left side
PLAYERSTATE_POSTCOSSCIN = 14 ; Post-Cossack cinematic sequence
PLAYERSTATE_WILYTRANS = 15 ; Wily transporter teleporting
PLAYERSTATE_COSSBOSSWLK = 16 ; The dual Cossack boss needs the Player to step forward a bit; that's this
PLAYERSTATE_GOTSPWEAPON = 17 ; Player got special weapon (e.g. Wire Adapter)
PLAYERSTATE_POSTWILY = 18 ; Sound_NoteTicksLeft defeating final Wily boss, Player walks left and holds
PLAYERSTATE_TELEPORTEND = 19 ; Sound_NoteTicksLeft defeating final Wily boss, Player teleports out and starts ending sequence
Player_State: .ds 1
Player_FaceDir: .ds 1 ; 1 for right, 2 for left
Player_ShootAnimTimer: .ds 1 ; Counts down to zero; as zero is reached, clears Player_CurShootAnim
; Player_CurShootAnim:
PLAYERCSA_NOSHOOT = 0
PLAYERCSA_SHOOT = 1
PLAYERCSA_THROW = 2
Player_CurShootAnim: .ds 1 ; An animation offset based on what's appropriate for the player's current "shooting" (generic way to offset for standing, walking, on ladder, etc.) -- basically, 0 = not shooting, 1 = shooting, 2 = throwing
Player_SlideTimer: .ds 1 ; Ticks down to zero; Player is sliding while active
Player_MBusterChargeLevel: .ds 1 ; $0035
Player_MBustDischargePalIdx: .ds 1 ; Palette index used immediately Sound_NoteTicksLeft discharging Mega Buster
; These together define the Player's horizontal movement speed.
Player_HCurSpeedFrac: .ds 1 ; "Fractional" component of Player's horizontal speed
Player_HCurSpeed: .ds 1 ; "Whole" component of Player's horizontal speed
; Player_TMWaterPush* -- override values used for Toad Man's
; "water pushing"; temporarily overrides Player's X velocity
; and facing direction to execute (restored after the move is
; made); enabled by Player_TMWaterPushFaceDir being non-zero
Player_TMWaterPushFaceDir: .ds 1 ; $0039
Player_TMWaterPushXVelFrac: .ds 1 ; $003A
Player_TMWaterPushXVel: .ds 1 ; $003B
Player_PlayerHitInv: .ds 1 ; $003C
Player_TriggerDeath: .ds 1 ; Triggers death sequence (always set to $FF? Does it matter?)
RAM_003E: .ds 1 ; $003E
Player_HitWallR_Flag: .ds 1 ; Will be set if Player walked right (direction) into wall, otherwise $00
Level_TileDetOff_Index: .ds 1 ; During tile detection loop, current index into the relevant "spread" array
Level_TileAttr_GreatestDet: .ds 1 ; The "greatest" tile attribute value detected last pass
Player_Y: .ds 1 ; $0042
Player_WaterPhysFudge: .ds 1 ; Remains at zero if not in water, cycles 0-3 otherwise, provides "boost" when jumping for floaty physics in water
Player_LastTileAttr: .ds 1 ; Last "greatest" (Level_TileAttr_GreatestDet) tile effect, used as a specialized temp
; Level_TileAttrsDetected:
; Up to 4 tile attributes to be detected (as detected, total amount specified by PRG062_TDetOffsetSpread table)
; Note this same space is also used as scratch vars periodically, because why not.
Level_TileAttrsDetected: .ds 4
; Ugly, but what can ya do
Temp_Var45 = Level_TileAttrsDetected+$00
Temp_Var46 = Level_TileAttrsDetected+$01
Temp_Var47 = Level_TileAttrsDetected+$02
Temp_Var48 = Level_TileAttrsDetected+$03
Temp_Var49: .ds 1 ; This one's totally temp though
RAM_004A: .ds 1 ; $004A
WeaponMenu_BackupPalAnims: .ds 5 ; Backs up palette animation data when opening weapons menu
; Vars for doing dynamic CHR RAM loading during gameplay
CHRRAMDL_LastPalLoad: .ds 1
CHRRAMDL_LastPalIndex: .ds 1
CHRRAMDLPtr_L: .ds 1
CHRRAMDLPtr_H: .ds 1
CHRRAMDL_BankLoadFrom: .ds 1
CHRRAMDL_GBufDataLen: .ds 1
CHRRAMDL_GBufDataSrcAddrL: .ds 1
CHRRAMDL_GBufDataSrcAddrH: .ds 1
CHRRAMDL_GBufVRAML: .ds 1
CHRRAMDL_GBufVRAMH: .ds 1
; ^ Ending overlaps those vars
EndTrainText_VRAMHigh = CHRRAMDL_LastPalLoad ; VRAM address high component of the text printing
EndTrainText_VRAMLow = CHRRAMDL_LastPalIndex ; VRAM address low component of the text printing
EndTrainText_TextPtrL = CHRRAMDLPtr_L ; Address low component of text printing
EndTrainText_TextPtrH = CHRRAMDLPtr_H ; Address high component of text printing
EndTrainText_NxtLnDelay = CHRRAMDL_BankLoadFrom ; Tick delay before next line of text
EndTrain_CurPalIndex = CHRRAMDL_GBufDataLen ; Current palette during train ending sequence
Level_TileDetOffPtr_L: .ds 1 ; $005A
Level_TileDetOffPtr_H: .ds 1 ; $005B
Raster_VSplit: .ds 1 ; Raster_VSplit_Req is copied to this value which is pushed to the MMC3 scanline regs
IntIRQ_FuncSel: .ds 1 ; Selects what function to call during scanline interrupt (-> IntIRQ_FuncPtr_L/H) (set by Raster_VMode)
IntIRQ_FS1_HScrl: .ds 1 ; Used only when IntIRQ_FuncSel = 1, sets the horizontal scroll register
.ds 1 ; unused
PalAnim_CurIndex: .ds 1 ; Current index of palette animation processing
; PalAnim_CommitCount: Number of palette animations run will set this count, including
; if a commit was due prior to animating. This count doesn't really have any use, I think
; it just exists to avoid any potential timing glitches with the palette commit during
; the NMI. Basically any non-zero value will commit the palette. See anim code for more.
PalAnim_CommitCount: .ds 1
PalAnim_PtrL: .ds 1 ; Palette animation pointer low
PalAnim_PtrH: .ds 1 ; Palette animation pointer high
; Pal_FadeMask: For palette fade in/out routines, can mask quadrants of colors that
; should not be effected by the fade. The LSB would be the last quadrant (the last
; 4 sprite colors) and the MSB would be the first quadrant (the first 4 BG colors.)
; BBBB SSSS
; 0123 0123
Pal_FadeMask: .ds 1 ; $0064
Raster_VSplit_VPos: .ds 1 ; $0065
Raster_VSplit_HPos: .ds 1 ; When doing a vertical raster scanline split, specifies the current horizontal position of the "upper half"; use Raster_VSplit_HPosReq to change
PPU_CTL1_PageBaseSel: .ds 1 ; Lower 2 bits OR'ed in with other PPU_CTL1 settings, specifically selecting the base nametable (i.e. wrap controls)
.ds 1 ; unused
Raster_VSplit_HPosReq: .ds 1 ; Requested horizontal value above a vertical raster scanline split; copied to Raster_VSplit_HPos
PPU_CTL1_PageBaseReq_RVBoss: .ds 1 ; $006A In certain raster modes, this gets pushed to PPU_CTL1_PageBaseSel
Player_EnergyGainCounter: .ds 1 ; $006B Just used as a counter for energy-gaining pickups, generally contained to PRG059, although a weird fringe use exists in PRG062 that never does anything
CineCsak_CurDialogSet: .ds 1 ; Current set of dialog during the post-Cossack cinematic dialog
CineCsak_TextOffset: .ds 1 ; Text offset witin dialog block during the post-Cossack cinematic dialog
CineCsak_TextPtrL: .ds 1 ; Base pointer to the dialog block during the post-Cossack cinematic dialog
CineCsak_TextPtrH: .ds 1 ; Base pointer to the dialog block during the post-Cossack cinematic dialog
.ds 16 ; unused?
; The "exec slots" seem to provide a stack outside of the 6502's stack.
; This enables the game to completely reset the 6502 stack for some reason.
; (Seems like a weird programming practice? But, all right, Capcom...)
;
; Basically an exec slot either contains an absolute jump address or it
; contains a stack register value that can be restored later. Sound_NoteTicksLeft a slot
; with flag 4 or 8 has been processed, the flag value is set to 2.
;
; 0: Flag:
; - 0: ?
; - 1: ?
; - 2: Exec slot fulfilled / not in use ???
; - 4: Only offset 2 of jump address is used and it holds a stack register value
; - 8: Jump address contains literal jump address to execute
; 1: Always 1?
; 2/3: Jump address
; - If flag is set to 4, only offset 2 is used, and it stores a stack register value
; - If flag is set to 8, jump address is an absolute jump address
ExecState_Slots: .ds 16 ; $0082
RAM_0090: .ds 1 ; $0090
ExecState_SlotDepth: .ds 1 ; Current depth into the exec slots
Frame_Counter: .ds 1 ; Increments every V-Blank
Temp_AddrL: .ds 1 ; $0093
Temp_AddrH: .ds 1 ; $0094
General_Counter: .ds 1 ; Free-running counter in level, also used on stage select to time the Dr. Cossack appearance flash
Spr_SlotIndex: .ds 1 ; $0096 Slot index for sprites ($00-$17)
Sprite_CurrentIndex: .ds 2 ; $0097 - $0098
Gravity: .ds 1 ; Current rate all objects are pulled down (usually $40, an underwater Metall sets it temporarily to $15)
DisFlag_NMIAndDisplay: .ds 1 ; Next NMI, the display and NMIs will be disabled
RasterSplit_En: .ds 1 ; 0 = Disable MMC3 raster split interrupt, 1 = enable (NOTE: MUST be 0 or 1 due to the way it's coded)
IntIRQ_FuncPtr_L: .ds 1 ; Low address for function executed on the scanline interrupt
IntIRQ_FuncPtr_H: .ds 1 ; High address for function executed on the scanline interrupt
RAM_009E: .ds 1 ; $009E (FIXME: Might be "rightmost object spawn index?")
RAM_009F: .ds 1 ; $009F (FIXME: Might be "leftmost object spawn index?")
; Currently select weapon (not enough to just change this as palette / graphics will be wrong)
PLAYERWPN_MEGABUSTER = 0 ; 0 = Mega Buster
PLAYERWPN_RUSHCOIL = 1 ; 1 = Rush coil
PLAYERWPN_RUSHJET = 2 ; 2 = Rush Jet
PLAYERWPN_RUSHMARINE = 3 ; 3 = Rush Marine
PLAYERWPN_TOADRAIN = 4 ; 4 = Toad Rain
PLAYERWPN_WIREADAPTER = 5 ; 5 = Wire adapter
PLAYERWPN_BALLOON = 6 ; 6 = Balloon
PLAYERWPN_DIVEMISSILE = 7 ; 7 = Dive Missile
PLAYERWPN_RINGBOOMERANG = 8 ; 8 = Ring
PLAYERWPN_DRILLBOMB = 9 ; 9 = Drill
PLAYERWPN_DUSTCRUSHER = 10 ; 10 = Dust
PLAYERWPN_PHARAOHSHOT = 11 ; 11 = Pharaoh
PLAYERWPN_FLASHSTOPPER = 12 ; 12 = Bright
PLAYERWPN_SKULLBARRIER = 13 ; 13 = Skull
Player_CurWeapon: .ds 1
Player_Lives: .ds 1 ; Player's lives
Player_EnergyTanks: .ds 1 ; Player's energy tanks
Player_LandPressLR: .ds 1 ; Player pressing LEFT/RIGHT at the moment they land
Player_OnIce: .ds 1 ; Set if Player is on ice
; Player_Land*: Variabels set immediately when Player lands
Player_SlipXVelFrac: .ds 1 ; Factor applied towards XVelFrac when slipping
Player_SlipXVel: .ds 1 ; Factor applied towards XVel when slipping
Player_LandXVelFrac: .ds 1 ; XVelFrac when Player landed
Player_LandXVel: .ds 1 ; XVel when Player landed
; Player_CompletedBosses: Bitfield of completed bosses
; $01 - Bright Man
; $02 - Toad Man
; $04 - Drill Man
; $08 - Pharaoh Man
; $10 - Ring Man
; $20 - Dust Man
; $40 - Dive Man
; $80 - Skull Man
Player_CompletedBosses: .ds 1
Player_CompletedFortLvls: .ds 1 ; Similar bitfield of completed Cossack/Wily fortress levels
WilyTrans_CurPortal: .ds 1 ; Last Wily transporter entered
LevelWily3_TransSysComp: .ds 1 ; Completed Wily Transport System bosses
Player_FreezePlayerTicks: .ds 1 ; Player is frozen while non-zero, counts to zero
.ds 2 ; Unused
; Player HP: $9C is max, $80 never-reached min, $00 if dead
Player_HP: .ds 1 ; $00B0
; Player_WpnEnergy: All the different weapon energy amounts
; $9C is max, $80 is min, $00 means you don't have it at all
;
; Offsets:
; 00 = Rush coil
; 01 = Rush Jet
; 02 = Rush Marine
; 03 = Toad Rain
; 04 = Wire adapter
; 05 = Balloon
; 06 = Dive Missile
; 07 = Ring
; 08 = Drill
; 09 = Dust
; 10 = Pharaoh
; 11 = Bright
; 12 = Skull
; 13 = Mega Buster (not really used, forced to $9C usually, though the meter is drawn like the others so it can be less than this)
Player_WpnEnergy: .ds 14
Boss_HP: .ds 1 ; Boss HP $00 min, $1C max (no bit 7 muck here)
; SndMus_DisFlags:
; FIXME: I don't think I understand this right... bit $01 set actually fetches data
; and updates SFX from the processing side
SndMus_DisFlags: .ds 1 ; Bit 0 ($01) set disables music and sound updating, Bit 1 ($02) set disables music updates only
MusSnd_TempVar0: .ds 1 ; TempVar used in music/sound code
MusSnd_TempVar1: .ds 1 ; TempVar used in music/sound code
MusSnd_TempVar2: .ds 1 ; TempVar used in music/sound code
MusSnd_TempVar3: .ds 1 ; TempVar used in music/sound code
MusSnd_PatchPtr_L: .ds 1 ; Low part of address of instrument patch data (from within PRG030_PatchData)
MusSnd_PatchPtr_H: .ds 1 ; High part of address of instrument patch data (from within PRG030_PatchData)
; Music tempo accumulator value
MusTempoAccum: .ds 1 ; This specifically is used to decrement ticks (so 2 is twice as fast as 1)
MusTempoAccum_Frac: .ds 1 ; Fractional accumulation for MusTempo_Frac
; Added to MusTempoAccum_H/L -- sets music speed
MusTempo: .ds 1 ; Sets the speed of tick decrement per frame (so 2 is twice as fast as 1)
MusTempo_Frac: .ds 1 ; Fractional component; can allow for fractional frame speeds
Mus_NoteTranspose: .ds 1 ; Added to note value
Mus_MasterVol_Dir: .ds 1 ; $00 means Mus_MasterVol = $00 is loudest, $80 means Mus_MasterVol = $FF is loudest
Mus_MasterVol: .ds 1 ; Sets master volume level; see Mus_MasterVol_Dir
Sound_CurSFXPriority: .ds 1 ; Arbitrary value assigned to sound effects to determine if a "more important" sound effect is currently playing (so an SFX with a lower priority number in its header cannot play)
; Sound_MusOverrideFlags: Each bit set disables a channel of music for sound playback
; $01 - Square 1
; $02 - Square 2
; $04 - Triangle
; $08 - Noise
Sound_MusOverrideFlags: .ds 1
SndPtr_L: .ds 1 ; $00D0
SndPtr_H: .ds 1 ; $00D1
Snd_NoteTranspose: .ds 1 ; $00D2 Added to note value for sound effects
Sound_RestTimer: .ds 1 ; $00D3 While non-zero, decrements to zero, pausing SFX processing
RAM_00D4: .ds 1 ; $00D4
RAM_00D5: .ds 1 ; $00D5
; Sound_LoopCounter
; Bit 7 ($80) is set if perpetual loop mode is enabled (not used by MM4, but fully functional)
; Bits 0-6 hold the loop counter value for the internal loop command (nothing to do with the perpetual loop mode)
Sound_LoopCounter: .ds 1 ; $00D6
; Sound_PostSFXQueueOff:
; If non-zero, This value will chain the SFX system to play a sound after
; the current one finishes. This does not reset after the sound plays so
; it loops forever until modified. This can be used to either loop a single
; sound or chain a sound so that it has a "start" and a "loop" section.
; This was not used by MM4, but I think MM5 did and maybe others.
;
; This value is based an offset into the address table, so the value
; (if non-zero) must be the same as "intended SFX_ (value * 2 + 1)", e.g.:
;
; If intended post-play sound is:
; SFX_PLAYERSHOT = $21
;
; value in Sound_PostSFXQueueOff should be $21 * 2 + 1 = $43
;
; Finally, value MUST be an SFX, NOT music
Sound_PostSFXQueueOff: .ds 1 ; $00D7
RAM_00D8: .ds 1 ; $00D8
Mus_Cur: .ds 1 ; $00D9
MusSnd_TriggerCurIdx: .ds 1 ; Current index into Sound_Trigger (queuing)
MusSnd_ProcessTriggerCurIdx: .ds 1 ; Current index into Sound_Trigger (processing)
Sound_Trigger: .ds 8 ; TODO: Describe better; 8 slots of sound triggers, $88 is idle, not fully sure how these work yet
RandomN: .ds 4 ; Array of randomly scribbled numbers
; ISR_BackupAddr: Backs up an ISR address when music/sound (FIXME) something happens
ISR_BackupAddr_L: .ds 1 ; $00E8
ISR_BackupAddr_H: .ds 1 ; $00E9
RAM_00EA: .ds 1 ; $00EA
SprObj_SlotIndex: .ds 1 ; $00EB
MMC3_PageA000_Backup: .ds 1 ; $00EC
RAM_00ED: .ds 1 ; $00ED
RAM_00EE: .ds 1 ; $00EE
Object_ReqBGSwitch: .ds 1 ; Set to $B8 or $01 to put current object in BG
; Raster_VMode: Change the current scanline split operation
; Will be copied into IntIRQ_FuncSel
RVMODE_NONE = $00 ; No split operation (normal scrolling)
RVMODE_RMBOSS = $01 ; Ring Man hippo sub-boss (Kabatoncue)
RVMODE_DMSBOSS = $02 ; Dive Man whale sub-boss (Moby)
RVMODE_CBOSS1 = $03 ; Cossack Boss 1 (giant moth thing Mothraya)
RVMODE_DUSTMANCRUSH = $04 ; Dust Man's crushing segment
RVMODE_DMWATER = $05 ; Dive Man's dynamic water level
RVMODE_CBOSS4 = $06 ; Cossack Boss 4 (Dr. Cossack)
RVMODE_CBOSS2 = $07 ; Cossack Boss 2 (the triple-mirrored split)
RVMODE_CINEDIALOG = $08 ; Cinematic dialog post-Cossack boss
RVMODE_WBOSS1 = $09 ; Wily Boss 1 (Giant Mettool)
RVMODE_INTROTRAIN1 = $0A ; Intro train scrolling sky
RVMODE_INTROTRAIN2 = $0B ; Intro train scrolling sky, train rolling away
Raster_VMode: .ds 1
Raster_VSplit_Req: .ds 1 ; Scanline where vertical split happens, copied to Raster_VSplit
MMC3_PrevCmd: .ds 1 ; Previous MMC3 command
MMC3_Page8000: .ds 1 ; Current bank at $8000
MMC3_PageA000: .ds 1 ; Current bank at $A000
MMC3_Page8000_Req: .ds 1 ; Requested bank to be at $8000
MMC3_PageA000_Req: .ds 1 ; Requested bank to be at $A000
MMC3_PageChng_InP: .ds 1 ; Non-zero during a page change operation
; MusSnd_NeedsUpdateInt:
; If a music/sound update was desired, but the interrupt occurred in the middle
; of a page switch (per PRG063_SetPRGBanks), this flag will be set. The next
; music/sound update will then be performed after PRG063_SetPRGBanks completes.
; THIS IS IMPORTANT because NOTHING changes MMC3 pages in the VBlank interrupt
; except music/sound, so this flag prevents a potential timing glitch that can
; occur if the interrupt happens between the MMC3 command being issued and
; the requested page!!
MusSnd_NeedsUpdateInt: .ds 1
Current_Screen: .ds 1 ; Current screen the player is on
Vert_Scroll: .ds 1 ; $00FA
RAM_00FB: .ds 1 ; $00FB
Horz_Scroll: .ds 1 ; $00FC
PPU_CTL1_PageBaseReq: .ds 1 ; $00FD Pushed to PPU_CTL1_PageBaseSel
PPU_CTL2_Copy: .ds 1 ; $00FE
PPU_CTL1_Copy: .ds 1 ; $00FF
; Spr_NoRespawnBits: Bytes which hold bitfields that mark objects to never respawn from the level set.
; So each byte represents 8 objects from the level data
; A bit being set here means to not spawn this object when the spawn cycle comes around.
; Since there's 32, that means there's a maximum of 256 supported objects (which makes sense)
Spr_NoRespawnBits: .ds 32 ; $0100 - $011F
; Ring Man's rainbow platforms, up to 4 could be active (but I don't think that can even happen)
RingManRainbowPlat_Data: .ds 4 ; Bit 7 ($80) sets it active, bit 6 ($40) requests an update, the lower 6 bits are its operating screen
RingManRainbowPlat_VH: .ds 4 ; VRAM Address High
RingManRainbowPlat_VL: .ds 4 ; VRAM Address Low
RingManRainbowPlat_Cnt: .ds 4 ; $012C - $012F
; HUDBar[P/W/B]_DispSetting (P = Player's HP, W = Player's Weapon, B = Boss) Energy bar:
; Bit $80 must be set for it to display at all
; The lower nibble specifies which offset from Player_HP to display.
; NOTE:
; HUDBarP_DispSetting will thus always be $80 or $00, since nothing else makes sense
; HUDBarW_DispSetting should be in the range of $81-$8D, or $00 if no weapon selected/etc.
; HUDBarB_DispSetting will thus always be $8F or $00, since nothing else makes sense
HUDBarP_DispSetting: .ds 1
HUDBarW_DispSetting: .ds 1
HUDBarB_DispSetting: .ds 1
Level_ExitTimeout: .ds 1 ; $0133 Counts to zero and exits level, but only if Player is in state PLAYERSTATE_TELEPORTOUT
Level_ExitTimeoutH: .ds 1 ; $0134 "High" part of Level_ExitTimeout, in use Sound_NoteTicksLeft dying only
; Level_LightDarkCtl:
; - $00: Bright
; - $01: Darkening
; - $40: Dark
; - $80: Brightening up
Level_LightDarkCtl: .ds 1
Level_LightDarkTransCnt: .ds 1 ; $0136
Level_LightDarkTransLevel: .ds 1 ; $0137
Player_WpnMenuCurSel: .ds 1 ; Current selection in weapon menu (does not indicate Player's current power-up, but is used to restore menu highlight when you return)
Player_WpnMenuLastSel: .ds 1 ; "Last" selected weapon (will be synced to Player_WpnMenuCurSel)
Weapon_ToadRainCounter: .ds 1 ; Counts down to zero; while active, rain effect is happening
Weapon_ToadRain_XYOff: .ds 1 ; Added to sprites X and Y to offset rain sprites
Weapon_FlashStopCnt: .ds 2 ; 16-bit flash stopper counter (non-zero, flash is active)
Level_RasterYOff: .ds 1 ; $013E
Level_RasterVDir: .ds 1 ; $013F
DustManCrsh_BlkShotOutScr: .ds 1 ; Screen of Dust Man crusher block to shoot out
DustManCrsh_BlkShotOutMeta: .ds 1 ; Metatile selector of Dust Man crusher block to shoot out
DustManCrsh_BlkShotOutCol: .ds 1 ; Column within metatile of Dust Man crusher block to shoot out
Level_DarkTimer: .ds 2 ; 16-bit counter while shrouded in darkness in Bright Man
ToadRain_OwnerIndex: .ds 1 ; Object index where the Toad Rain canister was spawned
Boss_SprIndex: .ds 1 ; Sprite object slot index where a boss resides
TileMap_IndexBackup: .ds 1 ; $0147
Level_EndLevel_Timeout: .ds 1 ; During PLAYERSTATE_ENDLEVEL, counts to zero before progressing
WilyTrans_LastTransParentIdx: .ds 1 ; ID of last transporter entered
; STACK RESERVATION
.ds 182 ; $014A - $01FF This is for stack space so don't use it
Stack_Bottom:
; Sprite memory is laid out in four bytes:
; Byte 0 - Stores the y-coordinate of the top left of the sprite minus 1.
; Byte 1 - Index number of the sprite in the pattern tables.
; Byte 2 - Stores the attributes of the sprite.
; * Bits 0-1 - Most significant two bits of the colour.
; * Bit 5 - Indicates whether this sprite has priority over the background.
; * Bit 6 - Indicates whether to flip the sprite horizontally.
; * Bit 7 - Indicates whether to flip the sprite vertically.
; Byte 3 - X coordinate
;
; Relevant flags
SPR_PAL0 = %00000000
SPR_PAL1 = %00000001
SPR_PAL2 = %00000010
SPR_PAL3 = %00000011
SPR_BEHINDBG = %00100000
SPR_HFLIP = %01000000
SPR_VFLIP = %10000000
Sprite_RAM: .ds 256 ; $0200
; Spr_SlotID:
; Indexes PRG062_Spr_Bank to select the source bank for the sprite data.
; Some exceptions are coded for the different sprite slot IDs.
; Further selected by the Spr_CurrentAnim.
; Must be non-zero or else sprite is not drawn.
;
; Some indexes are reserved for Player:
; 0: Main Player object
; 1-3: Player projectiles
; 4: Rush
; 5: Decorative Player sprites, e.g. "dust" when sliding, water splashes, etc.
Spr_SlotID: .ds 24 ; Sprite IDs
Spr_XVelFracAccum: .ds 24 ; Accumulator of fractional X velocity (to add Spr_XVelFrac and overflow into carry)
Spr_X: .ds 24 ; Sprite X position
Spr_XHi: .ds 24 ; $0348
Spr_YVelFracAccum: .ds 24 ; Accumulator of fractional Y velocity (to add Spr_YVelFrac and overflow into carry)
Spr_Y: .ds 24 ; Sprite Y position
Spr_YHi: .ds 24 ; $FF (-1) if above top of screen, 1 if below bottom of screen
Spr_XVelFrac: .ds 24 ; "Fractional" X velocity, which is added to Spr_XVelFracAccum which applies carry with Spr_XVel which is added to Spr_X
Spr_XVel: .ds 24 ; X velocity to objects (will be applied left/right by object's facing direction)
Spr_YVelFrac: .ds 24 ; "Fractional" Y velocity, which is added to Spr_YVelFracAccum which applies carry with Spr_YVel which is added to Spr_Y
Spr_YVel: .ds 24 ; Y velocity to objects (positive moves up)
; Spr_Flags2:
; Bits 0-5: [$00-$3F] Specifies a bounding box for the object (see PRG063_ObjBoundBoxWidth / PRG063_ObjBoundBoxHeight)
; Bit 6: If set, object is permitted to be shot (otherwise projectiles deflect)
; Bit 7: If set, object will hurt the Player if touched
SPRFL2_SHOOTABLE = $40
SPRFL2_HURTPLAYER = $80
Spr_Flags2: .ds 24 ; $0408
; Rough angles cooresponding to PRG063_Aim_FaceDir as generated by PRG063_AimTowardsPlayer and such
SPRAIM_ANG_0 = $00 ; Up
SPRAIM_ANG_22 = $01
SPRAIM_ANG_45 = $02 ; Up-right
SPRAIM_ANG_67 = $03
SPRAIM_ANG_90 = $04 ; Right
SPRAIM_ANG_112 = $05
SPRAIM_ANG_135 = $06 ; Down-right
SPRAIM_ANG_157 = $07
SPRAIM_ANG_180 = $08 ; Down
SPRAIM_ANG_202 = $09
SPRAIM_ANG_225 = $0A ; Down-left
SPRAIM_ANG_247 = $0B
SPRAIM_ANG_270 = $0C ; Left
SPRAIM_ANG_292 = $0D
SPRAIM_ANG_315 = $0E ; Up-Left
SPRAIM_ANG_337 = $0F
; Spr_FaceDir: Employed by PRG063_F413 or PRG063_F3ED
; Most importantly, "Up" will reverse the application of Y velocity,
; Left will clear hflip, right will set hflip, in one variant of the
SPRDIR_RIGHT = 1
SPRDIR_LEFT = 2
SPRDIR_DOWN = 4
SPRDIR_UP = 8
Spr_FaceDir: .ds 24 ; Sprite facing direction (1 for right, 2 for left, 4 for down, 8 for up)
Spr_SpawnParentIdx: .ds 24 ; Spawn index from level data that "owns" the level to prevent multiple spawns; $FF means "no parent" (e.g. dynamically spawned stuff)
Spr_HP: .ds 24 ; HP of object (not Player though)
Spr_Var1: .ds 24 ; NOTE: Player uses index 0 to hold the "previous state" when they get hit
Spr_Var2: .ds 24 ; $0480
Spr_Var3: .ds 24 ; $0498
Spr_Var4: .ds 24 ; NOTE: Player uses index 0 for Rush Jet riding flag
Spr_Var5: .ds 24 ; NOTE: Player uses index 0 for held Pharaoh Shot slot index
Spr_Var6: .ds 24 ; NOTE: Player uses index 0 for Flash Stopper background flashing
Spr_Var7: .ds 24 ; NOTE: In state PLAYERSTATE_POSTCOSSACK, acts as a countdown before cinematic kicks off
Spr_Var8: .ds 24 ; NOTE: Player uses index 0 for the Rush Marine decrement counter
; Spr_Flags: Note that a pretty common base bits set for typical objects is (SPRFL1_ONSCREEN | SPRFL1_SCREENREL)
SPRFL1_NOHITMOVEVERT = $01 ; Player detection disabled if moving vertically (e.g. Rush teleporting away should not longer respond to Player)
SPRFL1_OBJSOLID = $02 ; Object is fully solid if set
SPRFL1_NODRAW = $04 ; Don't draw if set
SPRFL1_PERSIST = $08 ; Persist if off-screen
SPRFL1_SCREENREL = $10 ; If set, object will be screen-relative
; $20 = SPR_BEHINDBG
; $40 = SPR_HFLIP
SPRFL1_ONSCREEN = $80 ; Set if object is on-screen
Spr_Flags: .ds 24 ; $0528
Spr_Frame: .ds 24 ; $0540
; Spr_CurrentAnim:
; Selects the sprite animation from within the bank as set by Spr_SlotID
; Must be non-zero or else no sprite is drawn
Spr_CurrentAnim: .ds 24
; Spr_AnimTicks: Animation ticks that count up for current animation frame
Spr_AnimTicks: .ds 24
; Spr_CodePtr: Object's currently executing code (initialized from default
; if Spr_CodePtrH bit $80 not set, so can't be in RAM I guess)
Spr_CodePtrL: .ds 24
Spr_CodePtrH: .ds 24
; Spr_FlashOrPauseCnt: When not zero, decrements towards zero (irrespective of bit $80) in two different modes
; If bit $80 is not set, do "flashing invincibility" effect (note: does not in and of itself actually grant invincibility)
; If bit $80 is set, halt animation and movement
Spr_FlashOrPauseCnt: .ds 24
; Unused space I think... multiple of 24, maybe was reserved sprite functionality?
.ds 48
PalData_1: .ds 32 ; "Current" palette
PalData_2: .ds 32 ; "Master copy" palette
Pattern_AttrBuffer: .ds 64 ; $0640 - $067F
; Level_ScreenTileModData: Variable purpose dynamic tile mod data
; Generally seems to be used to set a bit per tile row to mark it as "unsolid"
; Usages:
; Dust Man: Crusher segment, shootable blocks
; Ring Man: Rainbow platforms
Level_ScreenTileModData: .ds 64
Pattern_Buffer: .ds 16 ; $06C0
RAM_06D0: .ds 32
; PalAnim_EnSel: Index per BG palette quadrant:
; Bits 0-5 animation index (0 to 63) into PRG032_PalAnim_Table_L/H
; Bit 7 ($80) set enables animation on this quadrant
;
; NOTE: There's a hack for $87 specifically (enabled, index 7); see near PRG032_B8AD
; It animates the sprite palette with the BG palette
PalAnim_EnSel: .ds 4 ; $06F0-$06F3
; PalAnim_CurAnimOffset: Current offset within the palette animation data (PRG032_PalAnim_Table data offset 2+PalAnim_CurAnimOffset)
PalAnim_CurAnimOffset: .ds 4 ; $06F4 - $06F7
; PalAnim_TickCount: Current tick count for animation, limit set by byte 1 of (PRG032_PalAnim_Table data)
PalAnim_TickCount: .ds 4 ; $06F8 - $06FB
; UNUSED SPACE
.ds 4 ; $06FC - $06FF
; MUSIC FORMAT
;
; Music byte stream values >= 0x20 are a note
; Upper 3 bits select a delay value from PRG030_DelayTable1 or PRG030_DelayTable2 depending on bit 5 of Music_TrackOctaveTimingCtl
; If lower 5 bits are all zero, value is rest, otherwise it's a note select
;
; Music commands (byte stream values < 0x20)
MCMD_TOGGLE_TIME_SEL = $00 ; [0P] Toggle timing select
MCMD_TOGGLE_SINE_RESET = $01 ; [0P] Toggle sine reset
MCMD_SET_1_5X_TIMING = $02 ; [0P] Set 1.5x timing
MCMD_TOGGLE_OCTAVE_HIGH = $03 ; [0P] Toggle highest octave bit
; Music commands >= 0x04 implicitly obtain at one parameter byte, although additional ones may be fetched
MCMD_TRACK_CONFIG = $04 ; [1P] Set bits 3, 5, and 6 on Music_TrackOctaveTimingCtl according to parameter
MCMD_TEMPO_SET = $05 ; [2P] Set tempo MusTempo = parameter 1 and MusTempo_Frac = parameter 2
MCMD_NOTEATTACKLEN_SET = $06 ; [1P] Music_NoteAttackLength = parameter
MCMD_SYNTH_VOLUME_SET = $07 ; [1P] New synth vol = parameter (lower 4 bits only)