-
Notifications
You must be signed in to change notification settings - Fork 0
/
newbies.asm
2073 lines (1712 loc) · 69.9 KB
/
newbies.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
; dasm newbies.asm -onewbies.bin -lnewbies.lst -snewbies.sym -f3
; makewav -ts a.out
processor 6502
include "vcs.h"
SEG.U RAM_VARIABLES
ORG $80
; position and velocity
playerz ds 1 ; how far in to the maze
monster1z ds 1
playery ds 1 ; how close to splatting
monster1y ds 1
playerzlo ds 1 ; fractal part of their position
monster1zlo ds 1
playerylo ds 1
monster1ylo ds 1
playeryspeed ds 1 ; signed Y momentum
monster1yspeed ds 1
playerzspeed ds 1 ; signed Z momentum
monster1zspeed ds 1
; frame counter
tickcounter ds 1
; various
tmp1 ds 1
tmp2 ds 1
; level render
curplat ds 1 ; which platform we are rendering or considering; used as an index into the level0 table; incremented by 4 for each platform
deltaz ds 1 ; how far forward the current platform line is from the player
deltay ds 1 ; how far above or below the current platform the player is
lastline ds 1 ; last line rendered to the screen for gap filling
caller ds 1 ; vblank/vsync progress; used for this during all of the routines expect the display kernel XXX phasing this out but one thing aliases it
; aliases
; the display kernel re-uses render kernel locations
scanline = caller ; display kernel counts this down to zero and uses it to index the frame buffer
skyline = curplat ; offset in to the sky color data depending on our height
collision_bits = tmp1
collision_platform = tmp2
playerdata = tmp1 ; pointer to player graphic data used during render kernel
playerdatahi = tmp2
enemydata = curplat ; _drawenemies, how many lines of the enemy do we have left to draw?
; _drawenemies reuses lastline as the last line we're filling towards
; framebuffer
view ds [ $ff - view ] ; number of lines of display buffer, which will be about 100 or so; from $96 or so to $ff, the end of memory
viewsize = [ $ff - view ]
ECHO "viewsize: ", [viewsize]d
; 192 - 154
; plus 34 more scanlines for overscan
; roughly: n * 76 machine cycles / 64 cycle timer blocks
processinglines = 192 - viewsize + 30 ; number of lines (other than 37 vblank and 3 vsync) available for processing
ECHO "processinglines: ", [processinglines]d
processingtimer = processinglines * 76 / 64 - 1 ; value to use in TIM64T to measure about that much time (rounded down)
; converts between scanlines (76 cycles) and blocks on the 64 cycle timer
; not sure why the -1 was needed but that gets it back to 262 scanlines but I don't know if vblank kicks in when it's supposed to or if it's early and cycles need to be trimmed after
ECHO "processingtimer: ", [processingtimer]d
;
; constants
;
num_bodies = 2 ; playerz, monster1z, and so on for other variables
;
; momentum/gravity constants
;
flap_y = $06 ; $10 would take 16 frames to send us up one whole unit, if there was no decay
flap_z1 = $08 ; forward momentum added when flapping when stick pressed forward, neutral, and pulled back
flap_z2 = $05
flap_z3 = $fe
gravity = $01
runspeed = $0a
terminal_velocity = $100 - $78 ; ie -$78; $ff is -1
;
; macros
;
;
; _absolute
;
; takes a value in A, returns it in A
MAC _absolute
bpl .abs1
eor #$ff
clc
adc #$01
.abs1
ENDM
;
; apply momentum
;
; clobbers tmp1
; we should be done with the collision_bits and collision_platform by now though
MAC _momentum
;
; z speed
;
ldx #0 ; iterate over the objects to apply momentum to up to num_bodies
momentum0
; control loops back here when we iterate over the movable bodies
; z speed first
lda playerzspeed,x
bmi momentum1
; positive case
clc
adc playerzlo,x
sta playerzlo,x
bvc momentum2 ; overflow means we need to carry from our signed playerzlo into the unsigned playerz. if no overflow so we didn't go above 127. go on to dealing with Y speed.
clc
lda playerz,x
adc #01
; bcs momentum2 ; branch if we carried; don't wrap playerz above 255 XXX actually, wouldn't this be the win condition for the level?
sta playerz,x
jmp momentum2
momentum1
; playerzspeed is negative
_absolute ; absolute value of playerzspeed
sta tmp1
lda playerzlo,x
sec
sbc tmp1
sta playerzlo,x
lda playerz,x
; beq momentum1a ; but don't go below 0; XXX testing; actually, we do want enemies flying towards us to be able to warp off the end of the level XXX show enemy Z to see what's happening
sbc #0 ; if we barrowed above, this will effective decrement playerz by one
sta playerz,x
momentum1a
momentum2
; y speed
lda playeryspeed,x
bmi momentum3
; positive case
clc
adc playerylo,x
sta playerylo,x
bvc momentum4 ; no overflow so we didn't go above 127; skip to dealing with Y speed
clc
lda playery,x
adc #01
bcs momentum4 ; branch if we carried; don't wrap playery above 255
sta playery,x
jmp momentum4
momentum3
; playeryspeed is negative
_absolute ; absolute value of playeryspeed
sta tmp1
lda playerylo,x
sec
sbc tmp1
sta playerylo,x
lda playery,x
beq momentum3a ; but if Y is already zero, don't subtract any more from it; just sit there
sbc #0 ; if we borrowed above, this will effective decrement playery by one
sta playery,x
momentum3a
momentum4
;
; subtract gravity from vertical speed
;
; cap playeryspeed at terminal volicity
; from the signed comparison tutorial at http://www.6502.org/tutorials/compare_beyond.html#5
; "to compare the signed numbers NUM1 and NUM2, the subtraction NUM1-NUM2 is performed, and NUM1 < NUM2 when N eor V is 1, and NUM1 >= NUM2 when N eor V is 0"
; this method takes advantage of N being the same (and based on) bit 7 of the sbc result in A.
lda playeryspeed,x
sec
sbc #terminal_velocity
bvc momentum4a ; "f V is 0, N eor V = N, otherwise N eor V = N eor 1"
eor #%10000000 ; flip the sign bit if V is clear; this makes sense, since the definition of V is that it gets set when the result leaves the sign bit wrong
momentum4a
; "If the N flag is 1, then A (signed) < NUM (signed) and BMI will branch"
; "If the N flag is 0, then A (signed) >= NUM (signed) and BPL will branch"
bmi momentum4b ; if playeryspeed < terminal_velocity (negative numbers), branch to skip applying more gravity
lda playeryspeed,x ; apply gravity
sec
sbc #gravity
sta playeryspeed,x
momentum4b
;
; decay forward/backward momentum
;
momentum5
lda playerzspeed,x
beq momentum5c ; branch ahead if they aren't moving; there's nothing to decay;
bmi momentum5b
momentum5a
; player is moving forward; round towards zero
dec playerzspeed,x
jmp momentum5c
momentum5b
; player is moving backwards; round towards zero
inc playerzspeed,x
momentum5c
; done with forward/backward momentum decay
; lda playerzspeed
momentum9
inx
cpx #num_bodies
; beq momentum9a ; branch to exit if Y >= num_bodies
; jmp momentum0 ; else go back for another pass on the next movable body
bne momentum0
momentum9a
ENDM
;
; readstick
;
; expects the joystick bits (1 = pushed that direction) in X
; bit 3 = right, bit 2 = left, bit 1 = down, bit 0 = up, one stick per nibble
; we're looking at the left nibble here, which is the left stick
MAC _readstick
readstick
; cache the joystick bits in X
lda SWCHA
and #$f0
eor #$ff
tax
lda SWCHB
and #%00000010 ; select switch
bne readstick0 ; select switch enables test mode; branch to normal joystick logic if select switch is not pressed
teststick0
; readstick test mode; the joystick absolutely positions the player
teststick0a
; bit 0 = up
txa
and #%00010000
beq teststick1
ldy playery
cpy #$ff
beq teststick1 ; don't go over
inc playery
teststick1
; bit 1 = down
txa
and #%00100000
beq teststick5
ldy playery
cpy #$00
beq teststick5 ; don't go over
dec playery
teststick5
; bit 2 = left
txa
and #%01000000
beq teststick6
inc playerz
teststick6
; bit 3 = right
txa
and #%10000000
beq teststick7
dec playerz
teststick7
jmp readstick9
readstick0
; button
lda INPT4
bmi readstick8 ; branch if button not pressed (bit 7 stays 1 until the trigger is pressed, then it stays 0)
; button down
_cleartrigger
; bump playeryspeed
lda playeryspeed
clc
adc #flap_y ; $10 would take 16 frames to send us up one whole unit
bvs readstick1 ; don't write it back if it would overflow
sta playeryspeed
readstick1
; player is flapping; add more or less forward thrust depending on the stick
; bit 0 = up, which we interpret as meaning to accelerate as much as possible forward
txa
and #%00010000
beq readstick2
lda playerzspeed
clc
adc #flap_z1
bvs readstick2 ; don't write it back if it would overflow our signed playeryspeed
sta playerzspeed
jmp readstick4
readstick2
; bit 1 = down, which we take to mean to accelerate as little as possible forward
txa
and #%00100000
beq readstick3 ; if not pushing down either, then go to readstick3 which handles the neither forward nor backwards case
lda playerzspeed
clc
adc #flap_z3
bvs readstick4 ; don't write it back if it would overflow our signed playeryspeed
sta playerzspeed
jmp readstick4
readstick3
; neither forward nor back are pressed while flapping so accelerate forward a medium amount
lda playerzspeed
clc
adc #flap_z2
bvs readstick4 ; don't write it back if it would overflow our signed playeryspeed
sta playerzspeed
readstick4
; end forward/back/neutral stick testing during flap
jmp readstick9
readstick8
; button not pressed; forward/backwards only apply if we're on a platform XXXX
lda playeryspeed
bne readstick9 ; skip joystick control if the player has any Y speed; this could also have been done with collision_platform but I'm avoiding depending on those and this would be the only dependency. if we're on a platform, our Y speed got zerod, so use that as a sort of unreliable indicator. XXX
txa
and #%00010000 ; are we pushing up on the stick?
beq readstick8a ; branch to skip if not
lda #runspeed
sta playerzspeed ; rather than adding to speed, just set it; XXX would be better if we didn't do this if it would slow us down
jmp readstick9
readstick8a
txa
and #%00100000 ; are we pushing down on the stick?
beq readstick8b ; branch to skip if not
lda #-runspeed
sta playerzspeed ; rather than adding to speed, just set it
readstick8b
; fall through to end
readstick9
ENDM
;
; _arctan (formerly platlinedelta)
;
; takes deltaz and deltay
; returns the scanline to draw at in A
; index the arctan table with four bits of each delta
; we use the most significant non-zero four bits of each delta
; the arctangent table is indexed by the ratio of the y and z deltas
; this means we can scale the values up to get more precision as long as we scale them up together
; we shift right up to four times until ( deltay | delta ) <= 0x0f
; deltaz is in tmp1 and deltay is in tmp2 where they get shifted to the right
; this also adds half of the screen height or subtracts from half screen height as appropriate to convert to scan line number
; first stuff case of the deltay = 0 before we look at other stuffed cases; deltay = 0 platforms are drawn dead center of the screen
; we were stuffing that case, but then the arctangent table get fixed to handle it correctly so it got removed, then we started stuff
; deltaz = 0 to return either 0 or viewsize-1 and that broke that, so we started stuffing deltay = 0 again.
; XXXX since we now stuff Y=0, we can start the arctangent table on Y=1 for a little more precision
MAC _arctan
.platlinedelta
lda deltay
beq .platlinedeltastuff2 ; stuff the case where deltay = 0
_absolute
sta tmp2 ; tmp2 has abs(deltay)
cmp deltaz ; is deltaz = deltay?
bne .platlinedeltago ; branch on the normal case where deltaz != deltay
.platlinedeltastuff0
; deltaz = deltay or deltaz = 0; stuff the return value to be the very top or very bottom scanline, depending
bit deltay
bpl .platlinedeltastuff1
; platform is above us
lda #[viewsize - 1] ; is this correct? XXX or are we off by one?
jmp .platarctan9
.platlinedeltastuff1
; platform is below us
lda #0
jmp .platarctan9
.platlinedeltastuff2
lda #[viewsize/2]
bne .platarctan9
.platlinedeltago
lda deltaz
beq .platlinedeltastuff0 ; stuff the case where deltaz = 0; depending on deltay, we either return 0 or viewsize-1 for the scanline
sec
sbc tmp2
sta tmp1 ; tmp1 has deltaz - abs(deltay)
ora tmp2 ; combined bits so we only have to test one value to see if all bits are clear of the top nibble
.platlinedeltaagain
cmp #$0F
bmi .platlinedeltadone ; 0F is larger, had to barrow, so we know that no high nibble bits are set
lsr
lsr tmp1
lsr tmp2
jmp .platlinedeltaagain
.platlinedeltadone
lda tmp2 ; table loops over $z then over $y, so $z is down and $y is across
asl ; tmp1 is our deltaz, tmp2 our deltay
asl ; so we make tmp1 our high nibble so it goes down and tmp2 our low nibble so it goes across
asl
asl
ora tmp1
tay
bit deltay ; handle the separate cases of the platform above us and the platform below us
bpl .platarctan1 ; branch if deltay is positive; this means that the platform is lower than us
lda arctangent,y ; platform is above us; add the arctangent value to the middle of the screen
clc
adc #(viewsize/2) ; 'view' is upside down, so adding relative to the middle of it moves things towards the top of the screen
jmp .platarctan9
.platarctan1
lda #(viewsize/2) ; platform is below us; subtract the arctangent value from the middle of the screen
sec
sbc arctangent,y ; 'view' is upside down, so subtracting relative the middle of it moves things towards the bottom of the screen
; negative value indicates off screen angle; return the negative value to proprogate the error
.platarctan9
ENDM
;
; _plathypot
;
; use a hypotonose table to estimate distance to a line so that we can assign it a width to represent size
; projection: projected y = distance_of_"screen" * actual_y / distance_of_point
; we're just using angle as a direct index to scanline number, but I think that's okay... 0..45 degrees
; further away things move towards the middle of the screen, but atan(z/y) takes that into account
; compute line size:
; 1. zd,yd -> hypot table -> distance (adding zd back into zd a fractional number of times depending on yd)
; 2. distance -> perspective table -> size (looking distance in a table to figure out line width)
; #1 requires deltay to be less than 50, and we probably shouldn't be looking 50 paces ahead anyway
; this is just a situation optimized, table driven, unrolled multiplication
; it should probably be un-un-rolled a bit
MAC _plathypot
lda deltay
_absolute
tay
lda distancemods,y
sta tmp2 ; top three bits indicate whether 1/4th of deltaz should be re-added to itself, then 1/8th, etc
lda deltaz
lsr
lsr
sta tmp1 ; tmp1 contains the fractional (1/4 at first, then 1/8th, then 1/16th) value of deltaz
lda deltaz ; fresh copy to add fractional parts to
clc
asl tmp2
bcc .plathypot1
clc
adc tmp1 ; 1/4
.plathypot1
lsr tmp1 ; half again
asl tmp2
bcc .plathypot2
clc
adc tmp1 ; 1/8th
.plathypot2
lsr tmp1 ; half again
asl tmp2
bcc .plathypot3
clc
adc tmp1 ; 1/16th
.plathypot3
ENDM
;
; _plotonscreen
;
; Y gets the distance, which we use to figure out which size of line to draw
; X gets the scan line to draw at
; updates view[]
; uses tmp1 during the call to remember the new lastline
; this macro is used in three different places; okay, it's only used in one place with fatlines disabled but we're trying to add gap filling logic
; it, hold the current color in tmp2
MAC _plotonscreen
jmp .plotonscreen
; springboards-ish
.plot4
jmp .plot9
.plot5
jmp .plot_simple
.plotonscreen
cpx lastline ; are we drawing on top of the last line we drew for this platform?
beq .plot4 ; branch if nothing to do; go to the end; .plot9 is out of range now, so bouncing off of a springboard to it
.plot0
stx tmp1 ; hold the new lastline here until after we're done recursing to fill in the gaps; only do this when we're first called, not when we recurse
ldx curplat ; fetch the current platform color and stash it in tmp2 and then restore X to be the current line to draw at
lda level0+3,x
sta tmp2
ldx tmp1
lda lastline
bmi .plot5 ; .plot_simple is out of range; springboard to it; no last line if the highbit is set; use the simple case; out of range now
cpx lastline
bmi .plot_up0 ; branch if we're now drawing upwards relative the last plot; else we're drawing downwards relative the last plot
; otherwise, we're drawing downwards
.plot_down0
inc lastline ; pre-increment lastline so that we can do an equality comparision rather than a +/- 1 comparison
.plot_down1
lda view,x ; +4 9 ... get the line width of what's there already
and #%00011111 ; +2 11 ... mask off the color part and any other data
cmp perspectivetable,y ; +4 15 ... compare to the fatness of line we wanted to draw
bpl .plot_down2 ; +2 17 ... what we wanted to draw is smaller. that means it's further away. skip it. but still see about filling in gaps.
lda perspectivetable,y ; +4 21 ... perspectivetable translates distance to on-screen platform line width; 128 entries starting with 20s, winding down to 1s
ora tmp2 ; +3 24 ... add the platform color
sta view,x ; +4 28 ... draw to the framebuffer
.plot_down2
cpx lastline ; +3 31 ... we want to catch it at 1 diff, not equal but we can do that if we pre-inc lastline. this routine updates lastline anyway.
bne .plot_down2a
jmp .plot8 ; too far to beq to
.plot_down2a
dex ; +2 35 ... drawing downwards relative last plot
lda INTIM
cmp #4 ; XXXX observed 3
_vsync ; do the next vsync/vblank thing that needs to be done and put more time on the timer, if we're ready to start vsync.
jmp .plot_down1
; drawing upwards
.plot_up0
dec lastline ; pre-decrement lastline so that we can do a straight equality comparison
.plot_up1
lda view,x ; get the line width of what's there already
and #%00011111 ; mask off the color part and any other data
cmp perspectivetable,y ; compare to the fatness of line we wanted to draw
bpl .plot_up2 ; what we wanted to draw is smaller. that means it's further away. skip it. but still see about filling in gaps.
lda perspectivetable,y ; perspectivetable translates distance to on-screen platform line width; 128 entries starting with 20s, winding down to 1s
ora tmp2 ; add the platform color
sta view,x ; draw to the framebuffer
.plot_up2
cpx lastline
bne .plot_up2a
jmp .plot8 ; if lastline minus curline is exactly 1 away then our work is done; bail out
.plot_up2a
inx ; drawing upwards relative last plot
lda INTIM
cmp #4
_vsync
jmp .plot_up1 ; always branch; recurse back in
.plot_simple
; handle the simple case of not having any gap to fill
lda view,x ; get the line width of what's there already
and #%00011111 ; mask off the color part and any other data
cmp perspectivetable,y ; compare to the fatness of line we wanted to draw
bpl .plot8 ; what we wanted to draw is smaller. that means it's further away. skip it. but update lastline.
; actually plot this line on the screen
lda perspectivetable,y ; perspectivetable translates distance to on-screen platform line width; 128 entries starting with 20s, winding down to 1s
ora tmp2 ; add the platform color
sta view,x ; draw to the framebuffer and then just fallthrough to .plot8
; fall through
.plot8
ldx tmp1 ; after we're done recursing to fill in the gaps, update lastline
stx lastline
.plot9
ENDM
;
; _drawenemies
;
; update the frame buffer with enemy data
MAC _drawenemies
.drawenemies
ldy #1 ; object 0 is the player; start at object 1 XXX don't try to loop over multiple enemies yet, and when we do that, we'll need a memory location; lots of stuff in here uses Y
.drawenemies0
; compute deltaz, deltay
lda playerz,y ; deltaz = enemyz - playerz (positive is forward of us)
sec
sbc playerz
bpl .drawenemies0b ; branch to continue if enemy is >= players position
jmp .drawenemies8 ; loop and consider next enemy if this enemy is behind the player
.drawenemies0b
sta deltaz
lda playery ; deltay = playery - enemyy
sec
sbc playery,y
sta deltay ; okay if deltay is negative as long as deltaz >= deltay
.drawenemies0c
_absolute ; absolute of deltay
clc
cmp deltaz
bmi .drawenemies0a ; branch to continue on if Z>Y
beq .drawenemies0a ; branch to continue on if Z==Y
jmp .drawenemies8 ; out of the field of view; branch to try the next monster
.drawenemies0a
; clobbers tmp1 and tmp2
_arctan ; takes deltaz and deltay; uses tmp1 and tmp2 for scratch; returns an arctangent value in the accumulator from a table which we use as a scanline to draw too
.drawenemies0d ; unit test breakpoint
sta lastline ; scanline to draw at
; clobbers tmp1 and tmp2
_plathypot ; reads deltay and deltaz directly, returns the size aka distance of the line in the accumulator
; sta tmp1 ; record the line size for the purpose of z-buffering later
tax ; the line size for z-buffering
.drawenemies0e ; unit test breakpoint
ldy #0 ; if its out of range of our table, then use the smallest size; this is an index into the nusize table
cmp #20
bpl .drawenemies1
ldy #1 ; middle range
cmp #10
bpl .drawenemies1
ldy #2 ; close range
.drawenemies1
sty tmp2 ; Y contains the low two bits to draw, which is the players size
; compute starting and ending scan lines to draw this enemy on, and loop over them.
; on the last line that we draw, or if find that a bit of platform is closer than the enemy we're drawing, we specify the 0 offset into the pattern buffer, which is blank pattern data.
; XXX should also use one of three sprite stripts depending on range (close, middle, far)
lda #1 ; start at the top of the data in the enemy sprite strip
sta enemydata
lda lastline ; compute the first line to draw the player on to starting from the scanline the enemy is centered on
clc
adc enemyheights,y ; key the horizontal size off of the vertical size; the screen buffer is stored upside down so this is our start
.drawenemies1a ; unit test breakpoint
tay
; loop over the framebuffer lines
cpy #viewsize
bpl .drawenemies7 ; don't start drawing until we're actually on the screen; branch to iterate to the next line of enemy if the current line > viewsize
.drawenemies2
;
; do z-buffering using the arctan we saved in X
; the scanline to draw to is in Y
; from _plotonscreen
lda view,y ; get the line width of what's there already
and #%00011111 ; mask off the color part and any other data
cmp perspectivetable,x ; compare to the fatness of line we wanted to draw
bpl .drawenemies2a ; what we wanted to draw is smaller. that means it's further away. skip it.
; draw a line of our enemy bird
lda enemydata
asl
asl ; 000.xxx.00 sets the line of sprite data
ora tmp2 ; 000.000.xx sets the sprite width
ora #%11100000 ; 111.xxx.xx marks it as a sprite update line
bne .drawenemies2b ; always
.drawenemies2a
; enemy is obscured; stop drawing
lda #%11100000 ; clear sprite pattern buffer
; fall through
.drawenemies2b
; either the draw stop command gets written, or the size/graphics update does
sta view,y
.drawenemies7
; move to the next line of player graphic data to use
inc enemydata
; move to the next screen line to draw on
tya
clc
sbc tmp2 ; spread GRP0 updates out vertically to stretch the player out and allow chances to upldate the platform data; tmp2 is player size which is 0-2 plus 1 from carry
tay
cpy #viewsize
bpl .drawenemies7 ; don't start drawing until we're actually on the screen
; cpy lastline ; the scanline we pegged the enemy as standing on
; bpl .drawenemies2 ; if we're not on the last line, branch back up and loop; branch if current line (Y) < lastline
lda enemydata
cmp #8
bmi .drawenemies2
; turn off sprite drawing
lda #%11100000
sta view,y
.drawenemies8
; go to the next monster and loop back to the beginning if we haven't run out of them XXX disabled and we need to use a memory location for this iterator
; inx
; cpx #num_bodies
; bcs .drawenemies9 ; branch to exit if Y >= num_bodies
; jmp .drawenemies0 ; else go back for another pass on the next movable body
; .drawenemies9
ENDM
; _vsync
; preserve x and y
; if we're out of time, either start vblank and add more time to the timer, or else stop rendering
; caller=0 maens normal and overscan, caller=1 means vblank
; needs this sequence or something similar before it: lda INTIM, cmp #2 or just lda INTIM
MAC _vsync
bpl .vsync2 ; didn't go negative so still a little time on the clock
; out of time
lda caller
beq .vsync1 ; caller = 0 means not yet in vblank
.vsync0 ; caller = 1
; already in vblank
jmp nomoreplatforms
.vsync1 ; caller = 0
; burn down any time left on the clock
lda INTIM
bne .vsync1
; 3 scanlines of vsync signal
; this should happen on scanline 262, taking us back to scanline 1
lda #2
sta WSYNC ; XXX should hopefully roll us over to the start of scanline 259 XXX but check!
sta VSYNC
sta caller ; note that we're already in vsync; may do this differently XXX
sta WSYNC ; 260 XXX not sure about this count
sta WSYNC ; 261
sta WSYNC ; 262
lda #0
sta VSYNC
; start vblank
; 37 lines of vblank
lda #%01000010 ; leave the joystick latches (bit 7) on and don't reset them here; we do that elsewhere right after we read them; bit 2 sets VBLANK
sta VBLANK
lda #14 ; only just a bit more plat rendering before other things; adjusting this based on experimenting with stella XXX
sta TIM64T
; fall through to continue whereever we were invoked
.vsync2
ENDM
; _cleartrigger
;
; clear out latched joystick button presses
MAC _cleartrigger
lda #%00000000 ; bit 7 is joystick button latch enable; bit 2 is vblank enable XXX set bit 2 depending on whether we're currently running this during vblank
sta VBLANK
lda #%01000000
sta VBLANK
ENDM
; end macros, start linear code
;
; ROM
;
SEG PROGRAM_CODE
ORG $f000
;
; reset
;
reset
cld ; Clear decimal bit
; initialize ram to 0
lda #0
ldx #$80
reset0 sta $80,x
dex
bne reset0
; hardware
sta $281 ; all joystick pins for input
; player location on map
ldy #2
sty playerz
ldy #32
sty playery
; enemy location on map XXX should be random or something... but really, joust has a spawn sequence
ldy #10
sty playerz+1
ldy #60
sty playery+1
; some player setup
; burn some cycles relative start of scanline until the beam is at where we want to reset the player to being at (RESP0)
; this is how players are initially absolutely positioned
sta WSYNC
ldy #7
reset1
dey
bne reset1
lda #$55 ; color of player
sta COLUP0
sta RESP0 ; XXX move this around to be timed so that it positions P0 in the center of the screen; value doesn't matter; it's just a strobe
;
; platform graphics
;
startofframe
inc tickcounter
; wait for the timer to tick down so we can stay in sync
; then wait for sync, then do some setup in the 30 cycles we have before we have to be at the start of 'renderpump'
; startofframe0
; lda INTIM
; bne startofframe0
sta WSYNC ; 0
scanline1
lda #%00000001 ; +2 2 ... reflected playfield (bit 0 is 1); players have priority over playfield (bit 3 is 0) XXX can this be done once in reset and left?
sta CTRLPF ; +3 5
ldy #viewsize ; +2 7 ... indexes the view table and is our scanline counter
sty scanline ; +3 10
lda playery ; +3 13 ... 1/4th playery for picking background color for shaded sky/earth
lsr ; +2 15
lsr ; +2 17
sta skyline ; +2 20
nop ; +2 22 ... eat up some time
nop ; +2 24
nop ; +2 26
nop ; +2 28
nop ; +2 30
jmp renderpump ; +3 ... always; we need to arrive on cycle 33
; table of useful NUSIZ0 values; translates two bits to three bits; the %11 index is unused
nusize
.byte %00000000, %00000101, %00000111
; critically timed render loop
enemy
; control goes here to turn on/off drawing of an enemy and set its width instead of drawing a platform line
; A and X contain the current view[] for this scan line; the last five bits contain data specific to this case
; Y is 0
; we aren't set up to update COLUBK and PF* registers
; there are exactly 47 cycles on the clock when execution arrives here
tay ; +2 49 (47 when we arrive) ... make a copy of the view[] entry
and #%00000011 ; +2 51 ... the bottom two bits cointain the sprite width; 000, 101, 111 are the only reasonable values to plug in to NUSIZ0
tax ; +2 53 ... XXX with a large-ish table with a lot of redundancy in it, we could avoid having to back up A in Y, mask part off, then reload it from Y again
lda nusize,x ; +4 57 ... XXX this may not be necessary if we're happy to use 2 bits worth of data to set the player data read position... could give back 6 cycles
sta NUSIZ0 ; +3 60
; update player graphics data
tya ; +2 62
and #%00011100 ; +2 64 ... pick from 32 scan lines with a 4 scan line resolution; should be interesting
lsr ; +2 66 ... XXX this could be skipped
lsr ; +2 68 ... XXX this could be skipped
tay ; +2 70
lda (playerdata),y ; +5 75
sta GRP0 ; +3 ->2 (went up to 78 which rolls over 76 to 2; we head into hblank here and start a new scanline)
; update background color; this is a duplicate of code from the other code path
lda scanline ; +3 5
adc skyline ; +3 8
tay ; +2 10
lda background,y ; +4 14
sta COLUBK ; +3 17 (has to happen before cycle 22)
dec scanline ; +5 22
bmi renderdone ; +2 24 (counting the case where the branch isn't taken); XXX double check this one
nop ; +2 26 XXX wasting time
nop ; +2 28
nop ; +2 30 waste time
jmp renderpump ; +3 33 (have to get back to renderpump with exactly 33 cycles on the clock when we get there)
platforms
; we get 22 cycles before drawing starts, and then 76 total for the scan line
; 69 cycles; if we take out the wsync, we have 7 cycles left; that's enough to copy sprite data from a pre-computed table, but we already use all of our RAM. argh.
; 62 cycles! 14 cycles to spare.
sty COLUPF ; +3 3
tay ; +2 5
lda background,y ; +4 9
sta COLUBK ; +3 12
lda pf0lookup,x ; +4 16
sta PF0 ; +3 19 ... this needs to happen sometime on or before cycle 22
lda pf1lookup,x ; +4 23
sta PF1 ; +3 26 ... this needs to happen some time before cycle 28
lda pf2lookup,x ; +4 30
sta PF2 ; +3 33
renderpump
; get COLUPF, COLUBK, and scanline values ready to roll
; to shave a few cycles and minimize moving things around, try to put the pf*lookup index into S instead, and COLUPF in A? nope.
; would it be faster to put scanline back into RAM rather than trying to use S?
; control arrives here with exactly 33 cycles on the clock
ldy scanline ; +3 36 (33 when execution arrives)
; get value for COLUPF ready in Y
lax view,y ; +4 40
ldy platformcolors,x ; +4 44
; if the looked up color is $00, skip redrawing CULUPF, COLUBK, and the PF registers to instead update the player registers
beq enemy ; +2 46
; get the pf*lookup index ready in X
and #%00011111 ; +2 48
tax ; +2 50
; get value for COLUBK somewhat setup in A
lda scanline ; +3 53
adc skyline ; +3 56
nop ; +2 58 ... XXX 12 bonus cycles
nop ; +2 60
nop ; +2 62
nop ; +2 64
nop ; +2 66
nop ; +2 68
dec scanline ; +5 73
bpl platforms ; +3 76 (counting the case where it's taken; this has to come out to exactly 76 cycles)
renderdone
; renderdone happens on scanline 145 XXX update this
sta WSYNC ; don't start changing colors and pattern data until after we're done drawing the last platform line
lda #0
sta GRP0 ; turn sprite data off just in case it wasn't in the framebuffer instructions
; black to a back background, and blank out the playfield
sta COLUBK
sta PF0
sta PF1
sta PF2