POLARIS Author: Diminished Category: Christmas Challenge System: BBC Microcomputer System ("BBC Micro"), Model B, OS 1.2 Language: 6502 assembler Len source code: 3903 Len exe file: 67 Len code only: 67 Instructions: Warning: You may wish to turn the emulator's sound off before running this demo! The awful noise it makes is a by-product of some size optimisations, which will be discussed later. The program loops infinitely, but it does not crash. (For a slightly larger version which exits gracefully back to BASIC after completing, see later in this description.) The easiest emulator to use is the browser-based jsbeeb: https://bbcmicro.co.uk/jsbeeb/ Using jsbeeb, select "Discs" from the menu bar, then "From examples or local", and choose the "polaris.ssd" disc image file included with this ZIP. Then, either press Shift + F12 to autoboot the demo, or alternatively type *DEMO, and press Enter. This demo will only work on the Model B -- it will NOT WORK on the Master 128. (Here are some other viable emulators:) b-em: http://b-em.bbcmicro.com/ beebjit: https://github.com/scarybeasts/beebjit/ BeebEm for Windows: http://www.mkw.me.uk/beebem/ b2: https://github.com/tom-seddon/b2 The BBC Micro disc image "polaris.ssd" should contain the following files: DEMO the submitted demo binary (67 bytes) DEMO2 a slightly larger binary (71 bytes), which exits gracefully back to BASIC WINDEMO text windowing demonstration BASDEMO verbose BASIC version of demo Description: Acorn's BBC Micro features an operating system in ROM. By printing certain control character sequences, you can activate features of the OS's VDU driver (see https://beebwiki.mdfs.net/VDU for full details). Of interest here is VDU 28 (0x1c), which allows the user to define a text window. For example, you can write a BASIC program such as: 10 MODE 7 20 L=10 : REM left edge 30 B=17 : REM bottom edge 40 R=17 : REM right edge 50 T=10 : REM top edge 60 REM define text window: 70 VDU 28, L, B, R, T 80 REM same as CLS: 90 VDU 12 100 PRINT "Bismarck biss Mark, bis Mark Bismarck biss." (This example program is also included within the disc image "polaris.ssd"; type CHAIN "WINDEMO" at the BBC Micro BASIC prompt to run it. Note that on the BBC Micro, the double-quote character " is typed by pressing Shift + 2.) When run, this program will redefine the text window to be a small square in the middle of the screen, and the VDU 12 (clear screen) and PRINT commands will now operate within the boundary of this window. This demo abuses this text windowing capability to draw filled rectangles, by poking a value into a memory location (856) reserved by the operating system. In graphics modes, this location contains a background colour value that is used to fill the text window when it is cleared, but in the text-only Teletext mode (MODE 7), the ASCII value of this byte is used to fill the window instead. (The ? operator replaces POKE in BBC BASIC): ?856=42 Now, in screen mode 7 (Teletext), a CLS (or VDU 12) command will not clear the text window, but instead fill it with asterisks. This is useful. Although the demo itself is written in assembler, we will now consider a BASIC version for the purpose of explanation: 10 ?856=42 20 FOR X=-4 TO 4 30 M=17 + ABS X 40 Q=13 + X 50 VDU 28, Q, M, Q, 26-M 60 VDU 12 70 VDU 28, 26-M, Q, M, Q 80 VDU 12 90 VDU 26 100 NEXT (This example program is included within the disc image "polaris.ssd"; type CHAIN "BASDEMO" at the BBC Micro BASIC prompt to run it.) Line 10 sets up the OS poke which makes CLS fill the text window with asterisks, rather than clearing it. Each iteration of the FOR loop (line 20) will draw a cross, formed of two thin perpendicular rectangles intersecting at a point. X loops from -4 to +4, and a value Q = 13 + X is derived from X. The rectangles are drawn so they intersect at the point (Q, Q): Q +----->| | | * Q| * <-- 50 VDU 28, Q, M, Q, 26-M | * v * - ****\************ <-- 70 VDU 28, 26-M, Q, M, Q *\ * \ ... * \ <- intersection point (Q, Q) moves down and to the right * \ X=0 with each X increment * \ * \ ... (X=0 refers to the centre of the finished star shape.) * \ * \ X=4 * * * * The length of the rectangles drawn also needs to change as X increases, to form the 'V' shapes at the top, bottom, left and right of the star. The rectangles must shorten as they approach the centre of the star, then lengthen again once the centre has been passed. This is achieved by taking the absolute value of X in line 30, which will run from 4 to 0 (shortening) and then back to 4 (lengthening) as X increases. Two values -- M and (26-M) -- are derived from this absolute value of X in lines 30 and 40, forming the two ends of each rectangle. (Only horizontal rectangles are shown here; the vertical ones work just the same.) --- | M (right limit) | Q = 13 + X ---------------------->| | M = 17 + ABS(X) | | Q width = M - (26-M) 26-M | | (left limit) | | X | Q | ABS(X) | M | 26-M | width ------>| | v ---+----+--------+----+------+------- ***************** --- -4 | 9 | 4 | 21 | 5 | 16 | *************** -3 | 10 | 3 | 20 | 6 | 14 | ************* -2 | 11 | 2 | 19 | 7 | 12 | draw direction *********** -1 | 12 | 1 | 18 | 8 | 10 | ********* 0 | 13 | 0 | 17 | 9 | 8 | (assembler version *********** 1 | 14 | 1 | 18 | 8 | 10 | is different; see ************* 2 | 15 | 2 | 19 | 7 | 12 | diagram later on) *************** 3 | 16 | 3 | 20 | 6 | 14 | ***************** 4 | 17 | 4 | 21 | 5 | 16 V Each rectangle is filled with asterisks by a VDU 12 ("clear screen") statement. Finally, a VDU 26 statement is issued, which will un-define the text window. This is necessary to prevent the final asterisk from being scribbled by the '>' prompt character when the program terminates. Since lines 50, 60, 70, 80 and 90 are all VDU statements, a more compact BASIC version can combine these five VDU statements into one long VDU statement: VDU 28,Q,M,Q,26-M,7180;26-M,Q,M,Q,12,26 (The semicolon syntax used once here allows sending of a pair of VDU bytes as a 16-bit number "7180;" -- this saves one byte over the simpler sequence "12,28,"). The assembler port of this algorithm works similarly to the BASIC version. In the interests of size, it loops infinitely. Returning to BASIC would require an further condition check, an RTS, and a VDU 26 (to un-define the final text window so that the star is not corrupted by the '>' BASIC prompt). This would all add to the size of the code. A version of the code which _does_ return to BASIC may be found as "extra/polaris_return_to_basic_source.txt" in the ZIP file. An executable binary of this more graceful version is also included in "polaris.ssd": type *DEMO2 to run it. It is, however, 4 bytes larger than DEMO, so it is not the main submission. A 7-byte buffer is used to contain the VDU sequence bytes. At initialisation the buffer looks like this, although it will be populated with values as the code runs: .t_vdu_buf equb 12, 0, 0, 0, 9, 0, 28 This buffer is read backwards (right to left), and each byte is sent to the operating system via the OS system call OSWRCH (write character) by the following short subroutine, which is located immediately before the t_vdu_buf buffer: .S_vdu LDA t_vdu_buf, X JSR OSWRCH DEX BPL S_vdu RTS For horizontal rectangles this subroutine is called with X=6, and for vertical rectangles with X=5, which means that sometimes 7 values are sent to the operating system, and sometimes only 6. This is due to the following optimisation. Looking at the BASIC version from earlier, 50 VDU 28, Q, M, Q, 26-M <- vertical rectangle 70 VDU 28, 26-M, Q, M, Q <- horizontal rectangle Re-spacing these lines a little gives this: 50 VDU 28, Q, M, Q, 26-M 70 VDU 28, 26-M, Q, M, Q It may be seen that the byte sequence "Q, M, Q" is always sent to the OS, regardless of whether a horizontal or vertical rectangle is being drawn. Therefore there is no need to replace these values in t_vdu_buf after drawing a vertical rectangle; they can simply be left unchanged before drawing a horizontal one, and the operation can start from t_vdu_buf[6] rather than t_vdu_buf[5]. The assembler version exploits this to reduce the number of stores needed. Here is t_vdu_buf for both horizontal and vertical rectangle drawing: t_vdu_buf | vertical | horizontal index | rectangle (X=5) | rectangle (X=6) ----------+--------------------+------------------- [6] | | 28 VDU | [5] | 28 | 26-M bytes | [4] | Q | Q sent | [3] | M | M this | [2] | Q | Q way | [1] | 26-M | ????? V [0] | 12 (CLS) | 12 (CLS) This means, that for horizontal rectangles, position [1] in t_vdu_buf (marked with '?????') contains no meaningful VDU code. It is possible to NOP this out (e.g. with VDU 0) before each horizontal rectangle is drawn, but doing so would enlarge the code. Instead this location is simply left untouched, and so it continues to contain the 26-M value from the previous vertical rectangle, which is then interpreted by the operating system as a VDU code. This will cause the execution of a rogue VDU, from 5-9: VDU 5 Write text at graphics cursor VDU 6 Enable VDU drivers VDU 7 Make a short beep VDU 8 Backspace cursor one character VDU 9 Advance cursor one character Fortunately, none of these rogue VDU operations has any destructive impact, but the repeated execution of VDU 7 is the reason why the demo makes a constant, annoying beep. Getting rid of the beep would cost two bytes, so we don't do it. :) For the assembler version, the Q variable is kept in t_vdu_buf[4] (and then copied into t_vdu_buf[2] before the vertical rectangle drawing is done). The M variable is kept in t_vdu_buf[3]. The value of 26-M is not calculated arithmetically, but is just kept in the Y-register, and updated independently. As discussed before, the BASIC version presented earlier uses ABS(X) in order to obtain a value of M that decreases as it approaches the centre of the star and then increases again afterwards. To avoid the need to calculate ABS(X) in assembler, the star is now drawn in two halves, with the second half of the star being drawn in reverse compared to the BASIC version (so the rectangles are always narrowing). At the half-way point, Y is re-initialised to 5 and counts upwards, exactly as for the first half of the star, and M is re-initialised to 21 and counts downwards, again exactly as it does in the first half of the star. The change is that a self-modification is performed to change the "INC Q" instruction to a "DEC Q" instruction. Q is re-initialised to 17, and now counts downwards instead of upwards, which causes the second half of the star to be drawn in reverse order. These changes simulate the effect of the ABS(X) operation in the BASIC version. Here is a summary of how the assembler version draws things (again, vertical rectangles are omitted, but work the same way): --- M (right limit) | t_vdu_buf[3] | Q ---------------------->| | t_vdu_buf[4] | | Y register | | (left limit) | | Q | M | Y-reg | width ------>| | v ----+-----+-------+------- ***************** --- | 9 | 21 | 5 | 16 | *************** INC | 10 | 20 | 6 | 14 | first half ************* Q | 11 | 19 | 7 | 12 | drawn this way *********** v 12 | 18 | 8 | 10 v ********* 13 | 17 | 9 | 8 *********** ^ 14 | 18 | 8 | 10 ^ ************* DEC | 15 | 19 | 7 | 12 | second half drawn this way, *************** Q | 16 | 20 | 6 | 14 | with INC Q replaced by DEC Q ***************** | 17 | 21 | 5 | 16 | In order to detect when the half-way point has been reached, the Y-register is just compared against 10. The first half of the star is drawn once; the second half of the star is drawn an infinite number of times. Comments: Merry Christmas!