-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathelite-loader.asm
1297 lines (1005 loc) · 46 KB
/
elite-loader.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
\ ******************************************************************************
\
\ BBC MASTER ELITE GAME LOADER SOURCE
\
\ BBC Master Elite was written by Ian Bell and David Braben and is copyright
\ Acornsoft 1986
\
\ The code in this file has been reconstructed from a disassembly of the version
\ released on Ian Bell's personal website at http://www.elitehomepage.org/
\
\ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes
\ in the documentation are entirely my fault
\
\ The terminology and notations used in this commentary are explained at
\ https://elite.bbcelite.com/terminology
\
\ The deep dive articles referred to in this commentary can be found at
\ https://elite.bbcelite.com/deep_dives
\
\ ------------------------------------------------------------------------------
\
\ This source file contains the loader for BBC Master Elite.
\
\ ------------------------------------------------------------------------------
\
\ This source file produces the following binary file:
\
\ * M128Elt.bin
\
\ ******************************************************************************
INCLUDE "1-source-files/main-sources/elite-build-options.asm"
CPU 1 \ Switch to 65SC12 assembly, as this code runs on the
\ BBC Master
_SNG47 = (_VARIANT = 1)
_COMPACT = (_VARIANT = 2)
GUARD &C000 \ Guard against assembling over MOS memory
\ ******************************************************************************
\
\ Configuration variables
\
\ ******************************************************************************
CODE% = &0E00 \ The address where the code will be run
LOAD% = &0E00 \ The address where the code will be loaded
N% = 67 \ N% is set to the number of bytes in the VDU table, so
\ we can loop through them below
S% = &2C6C \ The address of the main entry point workspace in the
\ main game code
VIA = &FE00 \ Memory-mapped space for accessing internal hardware,
\ such as the video ULA, 6845 CRTC and 6522 VIAs (also
\ known as SHEILA)
OSWRCH = &FFEE \ The address for the OSWRCH routine
OSBYTE = &FFF4 \ The address for the OSBYTE routine
OSCLI = &FFF7 \ The address for the OSCLI routine
\ ******************************************************************************
\
\ Name: ZP
\ Type: Workspace
\ Address: &0070 to &0075
\ Category: Workspaces
\ Summary: Important variables used by the loader
\
\ ******************************************************************************
ORG &0002
IF _COMPACT
.MOS
SKIP 1 \ Determines whether we are running on a Master Compact
\
\ * 0 = This is a Master Compact
\
\ * &FF = This is not a Master Compact
ENDIF
ORG &0070
.ZP
SKIP 2 \ Stores addresses used for moving content around
.P
SKIP 1 \ Temporary storage, used in a number of places
.Q
SKIP 1 \ Temporary storage, used in a number of places
.YY
SKIP 1 \ Temporary storage, used in a number of places
.T
SKIP 1 \ Temporary storage, used in a number of places
ORG &00F4
.LATCH
SKIP 2 \ The RAM copy of the currently selected paged ROM/RAM
\ in SHEILA &30
\ ******************************************************************************
\
\ ELITE LOADER
\
\ ******************************************************************************
ORG CODE%
\ ******************************************************************************
\
\ Name: B%
\ Type: Variable
\ Category: Drawing the screen
\ Summary: VDU commands for setting the square mode 1 screen
\ Deep dive: The split-screen mode in BBC Micro Elite
\ Drawing monochrome pixels on the BBC Micro
\
\ ------------------------------------------------------------------------------
\
\ This block contains the bytes that get written by OSWRCH to set up the screen
\ mode (this is equivalent to using the VDU statement in BASIC).
\
\ It defines the whole screen using a square, monochrome mode 1 configuration;
\ the mode 2 part for the dashboard is implemented in the IRQ1 routine.
\
\ The top part of Elite's screen mode is based on mode 1 but with the following
\ differences:
\
\ * 64 columns, 31 rows (256 x 248 pixels) rather than 80, 32
\
\ * The horizontal sync position is at character 90 rather than 98, which
\ pushes the screen to the right (which centres it as it's not as wide as
\ the normal screen modes)
\
\ * Screen memory goes from &4000 to &7EFF
\
\ * In the Master version of Elite, the screen mode is actually based on mode
\ 129 rather than mode 1, so shadow RAM (known as LYNNE) is used to store
\ the screen memory, though in all other respects the screen mode is the
\ same as if it were based on mode 1
\
\ * The text window is 1 row high and 13 columns wide, and is at (2, 16)
\
\ * The cursor is disabled
\
\ This almost-square mode 1 variant makes life a lot easier when drawing to the
\ screen, as there are 256 pixels on each row (or, to put it in screen memory
\ terms, there are two pages of memory per row of pixels).
\
\ There is also an interrupt-driven routine that switches the bytes-per-pixel
\ setting from that of mode 1 to that of mode 2, when the raster reaches the
\ split between the space view and the dashboard.
\
\ ******************************************************************************
.B%
EQUB 22, 129 \ Switch to screen mode 129
EQUB 28 \ Define a text window as follows:
EQUB 2, 17, 15, 16 \
\ * Left = 2
\ * Right = 15
\ * Top = 16
\ * Bottom = 17
\
\ i.e. 1 row high, 13 columns wide at (2, 16)
EQUB 23, 0, 6, 31 \ Set 6845 register R6 = 31
EQUB 0, 0, 0 \
EQUB 0, 0, 0 \ This is the "vertical displayed" register, and sets
\ the number of displayed character rows to 31. For
\ comparison, this value is 32 for standard modes 1 and
\ 2, but we claw back the last row for storing code just
\ above the end of screen memory
EQUB 23, 0, 12, &08 \ Set 6845 register R12 = &08 and R13 = &00
EQUB 0, 0, 0 \
EQUB 0, 0, 0 \ This sets 6845 registers (R12 R13) = &0800 to point
EQUB 23, 0, 13, &00 \ to the start of screen memory in terms of character
EQUB 0, 0, 0 \ rows. There are 8 pixel lines in each character row,
EQUB 0, 0, 0 \ so to get the actual address of the start of screen
\ memory, we multiply by 8:
\
\ &0800 * 8 = &4000
\
\ So this sets the start of screen memory to &4000
EQUB 23, 0, 1, 64 \ Set 6845 register R1 = 64
EQUB 0, 0, 0 \
EQUB 0, 0, 0 \ This is the "horizontal displayed" register, which
\ defines the number of character blocks per horizontal
\ character row. For comparison, this value is 80 for
\ modes 1 and 2, but our custom screen is not as wide at
\ only 64 character blocks across
EQUB 23, 0, 2, 90 \ Set 6845 register R2 = 90
EQUB 0, 0, 0 \
EQUB 0, 0, 0 \ This is the "horizontal sync position" register, which
\ defines the position of the horizontal sync pulse on
\ the horizontal line in terms of character widths from
\ the left-hand side of the screen. For comparison this
\ is 98 for modes 1 and 2, but needs to be adjusted for
\ our custom screen's width
EQUB 23, 0, 10, 32 \ Set 6845 register R10 = %00100000 = 32
EQUB 0, 0, 0 \
EQUB 0, 0, 0 \ This is the "cursor start" register, and bits 5 and 6
\ define the "cursor display mode", as follows:
\
\ * %00 = steady, non-blinking cursor
\
\ * %01 = do not display a cursor
\
\ * %10 = fast blinking cursor (blink at 1/16 of the
\ field rate)
\
\ * %11 = slow blinking cursor (blink at 1/32 of the
\ field rate)
\
\ We can therefore turn off the cursor completely by
\ setting cursor display mode %01, with bit 6 of R10
\ clear and bit 5 of R10 set
\ ******************************************************************************
\
\ Name: Elite loader
\ Type: Subroutine
\ Category: Loader
\ Summary: Perform a number of OS calls, check for sideways RAM, load and
\ move the main game data, and load and run the main game code
\
\ ------------------------------------------------------------------------------
\
\ The loader loads and moves the following files. There is no decryption at this
\ stage - that is all done by the main game code.
\
\ * The BDATA game data file is loaded into main memory at &1300-&54FF, and is
\ then moved as follows:
\
\ * &1300-&21FF is moved to &7000-&7EFF in screen memory (i.e. shadow RAM)
\ for the dashboard
\
\ * &2200-&54FF is moved to &7F00-&B1FF in main memory, where the main
\ game code will decrypt it
\
\ * The main game code file is loaded into main memory at &1300 and the game
\ is started by jumping to &2C6C
\
\ The main game code file is called BCODE in the Master release and ELITE in the
\ Master Compact release. BCODE loads into &1300-&7F47, while ELITE loads into
\ &1300-&7FEC.
\
\ The main game code is then responsible for decrypting BDATA (from &8000 to
\ &B1FF) and BCODE/ELITE (from the end of the DEEOR routine to the end of the
\ file).
\
\ ******************************************************************************
.ENTRY
LDA #16 \ Call OSBYTE with A = 16 and X = 0 to set the ADC to
LDX #0 \ sample no channels from the joystick/Bitstik
JSR OSBYTE
IF _COMPACT
LDA #129 \ Call OSBYTE with A = 129, X = 0 and Y = &FF to detect
LDX #0 \ the machine type. This call is undocumented and is not
LDY #&FF \ the recommended way to determine the machine type
JSR OSBYTE \ (OSBYTE 0 is the correct way), but this call returns
\ the following:
\
\ * X = Y = &F5 if this is a Master Compact with MOS 5
LDA #&FF \ Set A = &FF, the value we want to store in the MOS
\ flag if this is not a Master Compact
CPX #&F5 \ If X <> &F5, skip the following instruction as this is
BNE P%+4 \ a Master Compact
LDA #0 \ This is a Master Compact, so set A = 0
STA MOS \ Store the value of A in MOS, which will be 0 if this
\ is a Master Compact, or &FF if it isn't
ENDIF
LDA #200 \ Call OSBYTE with A = 200, X = 1 and Y = 0 to disable
LDX #1 \ the ESCAPE key and disable memory clearing if the
JSR OSB \ BREAK key is pressed
LDA #13 \ Call OSBYTE with A = 13, X = 0 and Y = 0 to disable
LDX #0 \ the "output buffer empty" event
JSR OSB
LDA #144 \ Call OSBYTE with A = 144, X = 255 and Y = 0 to move
LDX #255 \ the screen down one line and turn screen interlace on
LDY #0
JSR OSBYTE
LDA #144 \ Repeat the above command, which has the effect of
LDX #255 \ setting the interlace to the original value, as the
JSR OSBYTE \ OSBYTE call above returns the original setting in Y
LDA #225 \ Call OSBYTE with A = 225, X = 128 and Y = 0 to set
LDX #128 \ the function keys to return ASCII codes for SHIFT-fn
JSR OSB \ keys (i.e. add 128)
LDA #13 \ Call OSBYTE with A = 13, X = 2 and Y = 0 to disable
LDX #2 \ the "character entering buffer" event
JSR OSB
LDA #LO(B%) \ Set ZP(1 0) to point to the VDU code table at B%
STA ZP
LDA #HI(B%)
STA ZP+1
LDY #0 \ We are now going to send the N% VDU bytes in the table
\ at B% to OSWRCH to set up the special mode 1 screen
\ that forms the basis for the split-screen mode
.LOOP
LDA (ZP),Y \ Pass the Y-th byte of the B% table to OSWRCH
JSR OSWRCH
INY \ Increment the loop counter
CPY #N% \ Loop back for the next byte until we have done them
BNE LOOP \ all (the number of bytes was set in N% above)
LDA #%00001111 \ Set the Access Control latch at SHEILA &34, as
STA VIA+&34 \ follows:
\
\ * Bit 7 = IRR = 0: Do not IRQ the CPU with this
\ * Bit 6 = TST = 0: Must be set to 0
\ * Bit 5 = IFJ = 0: &FC00-&FDFF maps to the 1Mhz bus
\ * Bit 4 = ITU = 0: CPU can access external co-pro
\ * Bit 3 = Y = 1: &C000-&DFFF set to 8K private RAM
\ * Bit 2 = X = 1: &3000-&7FFF set to 20K shadow RAM
\ * Bit 1 = E = 1: All shadow RAM locations accessible
\ * Bit 0 = D = 1: Display shadow RAM as screen memory
\
\ In short, this switches the screen memory, which is in
\ shadow RAM, into the memory map at &3000-&7FFF, so now
\ we can poke directly to the screen memory, and it also
\ maps the filing system RAM space into &C000-&DFFF
\ (HAZEL), in place of the MOS VDU workspace
JSR PLL1 \ Call PLL1 to draw Saturn
LDA #%00001001 \ Clear bits 1 and 2 of the Access Control latch at
STA VIA+&34 \ SHEILA &34, which changes the following:
\
\ * Bit 2 = X = 0: &3000-&7FFF set to main RAM
\ * Bit 1 = E = 0: VDU shadow RAM locations accessible
\
\ In short, this switches the screen memory, which is in
\ shadow RAM, out of the memory map, so &3000-&7FFF is
\ now mapped to main RAM and we can't update the screen
LDA #4 \ Call OSBYTE with A = 4, X = 1 and Y = 0 to disable
LDX #1 \ cursor editing, so the cursor keys return ASCII values
JSR OSB \ and can therefore be used in-game
LDA #9 \ Call OSBYTE with A = 9, X = 0 and Y = 0 to disable
LDX #0 \ flashing colours
JSR OSB
LDX #LO(MESS1) \ Set (Y X) to point to MESS1 ("L.BDATA FFFF1300")
LDY #HI(MESS1)
JSR OSCLI \ Call OSCLI to run the OS command in MESS1, which
\ loads the BDATA file to address &1300-&54FF, appending
\ &FFFF to the address to make sure it loads in the main
\ BBC Master rather than getting passed across the Tube
\ to the Second Processor, if one is fitted
LDA #6 \ Set the RAM copy of the currently selected paged ROM
STA LATCH \ to 6, so it matches the paged ROM selection latch at
\ SHEILA &30 that we are about to set
LDA VIA+&30 \ Set bits 0-3 of the ROM Select latch at SHEILA &30 to
AND #%11110000 \ 6, to switch sideways RAM bank 6 into &8000-&BFFF in
ORA #6 \ main memory
STA VIA+&30
LDA #%10101010 \ Set A and location &8000 to %10101010
STA &8000
LSR A \ Shift A and location &8000 right
LSR &8000
CMP &8000 \ If A matches location &8000 (i.e. both now contain
BEQ OK \ %01010101) then jump to OK, as ROM bank 6 is writable
\ and does indeed contain sideways RAM rather than a
\ paged ROM, which is what we need for running the game
BRK \ Otherwise we can't run the game, so terminate the
\ loader with the following error message
EQUB 0 \ Error number
EQUB 22, 7 \ Switch to mode 7 and clear the screen
EQUS "ELITE needs RAM in slot #6"
EQUB 0 \ End of error message
.OK
LDA #%00001111 \ Set bits 1 and 2 of the Access Control Register at
STA VIA+&34 \ SHEILA &34 to switch screen memory into &3000-&7FFF
\ We now want to copy &F pages of memory (&F00 bytes)
\ from &1300-&21FF to &7000-&7EFF in screen memory
LDX #&F \ Set a page counter in X to copy &F pages
LDA #&13 \ Set ZP(1 0) = &1300
STA ZP+1
STZ ZP
STZ P \ Set P(1 0) = &7000
LDA #&70
STA P+1
LDY #0 \ Set Y = 0 to act as a byte counter within each page
.MPL1
LDA (ZP),Y \ Copy the Y-th byte of the memory block at ZP(1 0) to
STA (P),Y \ the Y-th byte of the memory block at P(1 0)
DEY \ Decrement the byte counter
BNE MPL1 \ Loop back to copy the next byte until we have copied a
\ whole page of 256 bytes
INC ZP+1 \ Increment the high bytes of both ZP(1 0) and P(1 0)
INC P+1 \ so we copy the next page in memory
DEX \ Decrement the page counter
BNE MPL1 \ Loop back to copy the next page until we have done all
\ &F of them
LDA #%00001001 \ Clear bits 1 and 2 of the Access Control Register at
STA VIA+&34 \ SHEILA &34 to switch main memory back into &3000-&7FFF
\ We now want to copy &33 pages of memory (&3300 bytes)
\ from &2200-&54FF to &7F00-&B1FF in main memory
LDX #&33 \ Set a page counter in X to copy &33 pages
.MPL2
LDA (ZP),Y \ Copy the Y-th byte of the memory block at ZP(1 0) to
STA (P),Y \ the Y-th byte of the memory block at P(1 0)
DEY \ Decrement the byte counter
BNE MPL2 \ Loop back to copy the next byte until we have copied a
\ whole page of 256 bytes
INC ZP+1 \ Increment the high bytes of both ZP(1 0) and P(1 0)
INC P+1 \ so we copy the next page in memory
DEX \ Decrement the page counter
BNE MPL2 \ Loop back to copy the next page until we have done all
\ &33 of them
CLI \ Enable interrupts
LDX #LO(MESS2) \ Set (Y X) to point to MESS2 ("L.BCODE FFFF1300" in the
LDY #HI(MESS2) \ Master release, or "L.ELITE FFFF1300" in the Master
\ Compact release)
JSR OSCLI \ Call OSCLI to run the OS command in MESS2, which loads
\ the BCODE/ELITE file to address &1300-&7F48, appending
\ &FFFF to the address to make sure it loads in the main
\ BBC Master rather than getting passed across the Tube
\ to the Second Processor, if one is fitted
LDX #LO(MESS3) \ Set (Y X) to point to MESS3 ("DIR E")
LDY #HI(MESS3)
JSR OSCLI \ Call OSCLI to run the OS command in MESS3, which
\ changes the disc directory to E
LDA #6 \ Set the RAM copy of the currently selected paged ROM
STA LATCH \ to 6, so it matches the paged ROM selection latch at
\ SHEILA &30 that we are about to set
LDA VIA+&30 \ Switch ROM bank 6 into memory by setting bits 0-3 of
AND #%11110000 \ the ROM selection latch at SHEILA &30 to 6
ORA #6
STA VIA+&30
JMP S% \ Jump to the start of the main game code at S%, which
\ we just loaded in the BCODE/ELITE file
\ ******************************************************************************
\
\ Name: PLL1 (Part 1 of 3)
\ Type: Subroutine
\ Category: Drawing planets
\ Summary: Draw Saturn on the loading screen (draw the planet)
\ Deep dive: Drawing Saturn on the loading screen
\
\ ******************************************************************************
.PLL1
\ The following loop iterates CNT(1 0) times, i.e. &300
\ or 768 times, and draws the planet part of the
\ loading screen's Saturn
STA RAND+1 \ Store A in RAND+1 among the hard-coded random seeds
\ in RAND. We set A to %00001111 before calling the PLL1
\ routine, so this sets the random number generator so
\ that it always generates the same numbers every time,
\ which is probably not what was intended (other
\ versions read the 6522 System VIA timer to use as a
\ seed, which is random). As a result, if you look at
\ the Saturn on the Master loading screen, it is always
\ exactly the same, every time you run the game
JSR DORND \ Set A and X to random numbers, say A = r1
JSR SQUA2 \ Set (A P) = A * A
\ = r1^2
STA ZP+1 \ Set ZP(1 0) = (A P)
LDA P \ = r1^2
STA ZP
JSR DORND \ Set A and X to random numbers, say A = r2
STA YY \ Set YY = A
\ = r2
JSR SQUA2 \ Set (A P) = A * A
\ = r2^2
TAX \ Set (X P) = (A P)
\ = r2^2
LDA P \ Set (A ZP) = (X P) + ZP(1 0)
ADC ZP \
STA ZP \ first adding the low bytes
TXA \ And then adding the high bytes
ADC ZP+1
BCS PLC1 \ If the addition overflowed, jump down to PLC1 to skip
\ to the next pixel
STA ZP+1 \ Set ZP(1 0) = (A ZP)
\ = r1^2 + r2^2
LDA #1 \ Set ZP(1 0) = &4001 - ZP(1 0) - (1 - C)
SBC ZP \ = 128^2 - ZP(1 0)
STA ZP \
\ (as the C flag is clear), first subtracting the low
\ bytes
LDA #&40 \ And then subtracting the high bytes
SBC ZP+1
STA ZP+1
BCC PLC1 \ If the subtraction underflowed, jump down to PLC1 to
\ skip to the next pixel
\ If we get here, then both calculations fitted into
\ 16 bits, and we have:
\
\ ZP(1 0) = 128^2 - (r1^2 + r2^2)
\
\ where ZP(1 0) >= 0
JSR ROOT \ Set ZP = SQRT(ZP(1 0))
LDA ZP \ Set X = ZP >> 1
LSR A \ = SQRT(128^2 - (a^2 + b^2)) / 2
TAX
LDA YY \ Set A = YY
\ = r2
CMP #128 \ If YY >= 128, set the C flag (so the C flag is now set
\ to bit 7 of A)
ROR A \ Rotate A and set the sign bit to the C flag, so bits
\ 6 and 7 are now the same, i.e. A is a random number in
\ one of these ranges:
\
\ %00000000 - %00111111 = 0 to 63 (r2 = 0 - 127)
\ %11000000 - %11111111 = 192 to 255 (r2 = 128 - 255)
\
\ The PIX routine flips bit 7 of A before drawing, and
\ that makes -A in these ranges:
\
\ %10000000 - %10111111 = 128-191
\ %01000000 - %01111111 = 64-127
\
\ so that's in the range 64 to 191
JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at
\
\ (ZP / 2, -A)
\
\ where ZP = SQRT(128^2 - (r1^2 + r2^2))
\
\ So this is the same as plotting at (x, y) where:
\
\ r1 = random number from 0 to 255
\ r2 = random number from 0 to 255
\ (r1^2 + r2^2) < 128^2
\
\ y = r2, squished into 64 to 191 by negation
\
\ x = SQRT(128^2 - (r1^2 + r2^2)) / 2
\
\ which is what we want
.PLC1
DEC CNT \ Decrement the counter in CNT (the low byte)
BNE PLL1 \ Loop back to PLL1 until CNT = 0
DEC CNT+1 \ Decrement the counter in CNT+1 (the high byte)
BNE PLL1 \ Loop back to PLL1 until CNT+1 = 0
\ ******************************************************************************
\
\ Name: PLL1 (Part 2 of 3)
\ Type: Subroutine
\ Category: Drawing planets
\ Summary: Draw Saturn on the loading screen (draw the stars)
\ Deep dive: Drawing Saturn on the loading screen
\
\ ******************************************************************************
\ The following loop iterates CNT2(1 0) times, i.e. &1DD
\ or 477 times, and draws the background stars on the
\ loading screen
.PLL2
JSR DORND \ Set A and X to random numbers, say A = r3
TAX \ Set X = A
\ = r3
JSR SQUA2 \ Set (A P) = A * A
\ = r3^2
STA ZP+1 \ Set ZP+1 = A
\ = r3^2 / 256
JSR DORND \ Set A and X to random numbers, say A = r4
STA YY \ Set YY = r4
JSR SQUA2 \ Set (A P) = A * A
\ = r4^2
ADC ZP+1 \ Set A = A + r3^2 / 256
\ = r4^2 / 256 + r3^2 / 256
\ = (r3^2 + r4^2) / 256
CMP #&11 \ If A < 17, jump down to PLC2 to skip to the next pixel
BCC PLC2
LDA YY \ Set A = r4
JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at
\ (r3, -r4), where (r3^2 + r4^2) / 256 >= 17
\
\ Negating a random number from 0 to 255 still gives a
\ random number from 0 to 255, so this is the same as
\ plotting at (x, y) where:
\
\ x = random number from 0 to 255
\ y = random number from 0 to 255
\ HI(x^2 + y^2) >= 17
\
\ which is what we want
.PLC2
DEC CNT2 \ Decrement the counter in CNT2 (the low byte)
BNE PLL2 \ Loop back to PLL2 until CNT2 = 0
DEC CNT2+1 \ Decrement the counter in CNT2+1 (the high byte)
BNE PLL2 \ Loop back to PLL2 until CNT2+1 = 0
\ ******************************************************************************
\
\ Name: PLL1 (Part 3 of 3)
\ Type: Subroutine
\ Category: Drawing planets
\ Summary: Draw Saturn on the loading screen (draw the rings)
\ Deep dive: Drawing Saturn on the loading screen
\
\ ******************************************************************************
\ The following loop iterates CNT3(1 0) times, i.e. &333
\ or 819 times, and draws the rings around the loading
\ screen's Saturn
.PLL3
JSR DORND \ Set A and X to random numbers, say A = r5
STA ZP \ Set ZP = r5
JSR SQUA2 \ Set (A P) = A * A
\ = r5^2
STA ZP+1 \ Set ZP+1 = A
\ = r5^2 / 256
JSR DORND \ Set A and X to random numbers, say A = r6
STA YY \ Set YY = r6
JSR SQUA2 \ Set (A P) = A * A
\ = r6^2
STA T \ Set T = A
\ = r6^2 / 256
ADC ZP+1 \ Set ZP+1 = A + r5^2 / 256
STA ZP+1 \ = r6^2 / 256 + r5^2 / 256
\ = (r5^2 + r6^2) / 256
LDA ZP \ Set A = ZP
\ = r5
CMP #128 \ If A >= 128, set the C flag (so the C flag is now set
\ to bit 7 of ZP, i.e. bit 7 of A)
ROR A \ Rotate A and set the sign bit to the C flag, so bits
\ 6 and 7 are now the same
CMP #128 \ If A >= 128, set the C flag (so again, the C flag is
\ set to bit 7 of A)
ROR A \ Rotate A and set the sign bit to the C flag, so bits
\ 5-7 are now the same, i.e. A is a random number in one
\ of these ranges:
\
\ %00000000 - %00011111 = 0-31
\ %11100000 - %11111111 = 224-255
\
\ In terms of signed 8-bit integers, this is a random
\ number from -32 to 31. Let's call it r7
ADC YY \ Set A = A + YY
\ = r7 + r6
TAX \ Set X = A
\ = r6 + r7
JSR SQUA2 \ Set (A P) = A * A
\ = (r6 + r7)^2
TAY \ Set Y = A
\ = (r6 + r7)^2 / 256
ADC ZP+1 \ Set A = A + ZP+1
\ = (r6 + r7)^2 / 256 + (r5^2 + r6^2) / 256
\ = ((r6 + r7)^2 + r5^2 + r6^2) / 256
BCS PLC3 \ If the addition overflowed, jump down to PLC3 to skip
\ to the next pixel
CMP #80 \ If A >= 80, jump down to PLC3 to skip to the next
BCS PLC3 \ pixel
CMP #32 \ If A < 32, jump down to PLC3 to skip to the next pixel
BCC PLC3
TYA \ Set A = Y + T
ADC T \ = (r6 + r7)^2 / 256 + r6^2 / 256
\ = ((r6 + r7)^2 + r6^2) / 256
CMP #16 \ If A >= 16, skip to PL1 to plot the pixel
BCS PL1
LDA ZP \ If ZP is positive (i.e. r5 < 128), jump down to PLC3
BPL PLC3 \ to skip to the next pixel
.PL1
\ If we get here then the following is true:
\
\ 32 <= ((r6 + r7)^2 + r5^2 + r6^2) / 256 < 80
\
\ and either this is true:
\
\ ((r6 + r7)^2 + r6^2) / 256 >= 16
\
\ or both these are true:
\
\ ((r6 + r7)^2 + r6^2) / 256 < 16
\ r5 >= 128
LDA YY \ Set A = YY
\ = r6
JSR PIX \ Draw a pixel at screen coordinate (X, -A), where:
\
\ X = (random -32 to 31) + r6
\ A = r6
\
\ Negating a random number from 0 to 255 still gives a
\ random number from 0 to 255, so this is the same as
\ plotting at (x, y) where:
\
\ r5 = random number from 0 to 255
\ r6 = random number from 0 to 255
\ r7 = r5, squashed into -32 to 31
\
\ x = r6 + r7
\ y = r6
\
\ 32 <= ((r6 + r7)^2 + r5^2 + r6^2) / 256 < 80
\
\ Either: ((r6 + r7)^2 + r6^2) / 256 >= 16
\
\ Or: ((r6 + r7)^2 + r6^2) / 256 < 16
\ r5 >= 128
\
\ which is what we want
.PLC3
DEC CNT3 \ Decrement the counter in CNT3 (the low byte)
BNE PLL3 \ Loop back to PLL3 until CNT3 = 0
DEC CNT3+1 \ Decrement the counter in CNT3+1 (the high byte)
BNE PLL3 \ Loop back to PLL3 until CNT3+1 = 0
\ ******************************************************************************
\
\ Name: DORND
\ Type: Subroutine
\ Category: Maths (Arithmetic)
\ Summary: Generate random numbers
\ Deep dive: Generating random numbers
\ Fixing ship positions
\
\ ------------------------------------------------------------------------------
\
\ Set A and X to random numbers (though note that X is set to the random number
\ that was returned in A the last time DORND was called).
\
\ The C and V flags are also set randomly.
\
\ This is a simplified version of the DORND routine in the main game code. It
\ swaps the two calculations around and omits the ROL A instruction, but is
\ otherwise very similar. See the DORND routine in the main game code for more
\ details.
\
\ ******************************************************************************
.DORND
LDA RAND+1 \ r1´ = r1 + r3 + C
TAX \ r3´ = r1
ADC RAND+3
STA RAND+1
STX RAND+3
LDA RAND \ X = r2´ = r0
TAX \ A = r0´ = r0 + r2
ADC RAND+2
STA RAND
STX RAND+2
RTS \ Return from the subroutine
\ ******************************************************************************
\
\ Name: RAND
\ Type: Variable
\ Category: Drawing planets
\ Summary: The random number seed used for drawing Saturn
\
\ ******************************************************************************
.RAND
EQUD &34785349
\ ******************************************************************************
\
\ Name: SQUA2
\ Type: Subroutine
\ Category: Maths (Arithmetic)
\ Summary: Calculate (A P) = A * A
\ Deep dive: Shift-and-add multiplication
\
\ ------------------------------------------------------------------------------
\
\ Do the following multiplication of signed 8-bit numbers:
\
\ (A P) = A * A
\
\ This uses a similar approach to routine SQUA2 in the main game code, which
\ itself uses the MU11 routine to do the multiplication. However, this version
\ first ensures that A is positive, so it can support signed numbers.
\
\ ******************************************************************************
.SQUA2
BPL SQUA \ If A > 0, jump to SQUA
EOR #&FF \ Otherwise we need to negate A for the SQUA algorithm
CLC \ to work, so we do this using two's complement, by
ADC #1 \ setting A = ~A + 1
.SQUA
STA Q \ Set Q = A and P = A
STA P \ Set P = A
LDA #0 \ Set A = 0 so we can start building the answer in A
LDY #8 \ Set up a counter in Y to count the 8 bits in P
LSR P \ Set P = P >> 1
\ and C flag = bit 0 of P
.SQL1
BCC SQ1 \ If C (i.e. the next bit from P) is set, do the
CLC \ addition for this bit of P:
ADC Q \
\ A = A + Q
.SQ1
ROR A \ Shift A right to catch the next digit of our result,
\ which the next ROR sticks into the left end of P while
\ also extracting the next bit of P
ROR P \ Add the overspill from shifting A to the right onto
\ the start of P, and shift P right to fetch the next
\ bit for the calculation into the C flag
DEY \ Decrement the loop counter
BNE SQL1 \ Loop back for the next bit until P has been rotated
\ all the way
RTS \ Return from the subroutine
\ ******************************************************************************
\
\ Name: PIX
\ Type: Subroutine
\ Category: Drawing pixels
\ Summary: Draw a single pixel at a specific coordinate
\
\ ------------------------------------------------------------------------------
\