I'll start with David Nowotnik's Lunar Lander - this was the program where I can get the UDGs to show up on QemuLator, but not on QLAY (or QPC2 either, I just tried that and the graphics are shown as the standard accented characters, though I'll put that down to SMSQ/E being strange about it - just thought it was worth trying).
Here are the processes I've been through, with the observations:
(1) I deleted everything in the Lunar Lander listing except the procedures "initialise" (sets up the screen with all the windows), "udgs" (to perform the black arts, redefining the characters 128-137), and "rocket (x)" (to draw the lander with thrust level x from 0 to 9). So all it would do was draw the lander in window #3 now, and it worked. I tried different CSIZEs and MODE 4, and the UDGs still showed up and made the lander.
(2) I opened a new channel #9, gave it a PAPER colour and CLS, to provide a background to what I was going to do - with the line in the program before opening channels #3, #4 and #5. Trying the "rocket" procedure no longer showed the UDGs - they were returned to the usual accented lower-case characters. Opening #9 and performing all its colour operations after channels #3, #4 and #5 made the UDGs show up again in channel #3. This does at least show that the order that channels are opened is important - presumably they're being assigned to different parts of memory. But, as long as channel #3 is opened first, I should be able to get UDGs to print in it if I don't change the code too much.
(3) Deleting or closing all the channels above #3 did not affect the UDGs.
(4) A more radical change was to open channels #3 to #9 first (just OPEN #n,scr without all the 512x256a0x0 afterwards), then define all the windows 1-8 so that these are small, strip-shaped windows on top of #9. I wrote a short routine that would print the characters 128-190 in all windows 0-8, to see what would show up. As I expected, the UDGs are only shown in channel #3, with the initially-defined "failure" character printed for CHR$ 138 to 190, which ar undefined by this program.
(5) I also tried changing the pattern of the "failure" character, and this showed up in channel #3 as expected.
The listing in the "code" tags below is what I've done so far - the "udgs" procedure is still recognisable as David Nowotnik's original, even if I have annotated it with a bunch of REMs:
Code: Select all
100 windows
110 udgs
120 FOR w=0 to 8: test w: NEXT w
650 STOP
1000 REMark *****************************
1010 DEFine PROCedure windows
1020 REMark *****************************
1030 LOCal n
1040 MODE 4
1050 FOR n=3 TO 8: OPEN #n,scr: NEXT n
1060 OPEN #9,scr_512x206a0x0: PAPER #9,2: CLS #9
1070 FOR n=1 TO 8
1080 WINDOW #n,500,20,6,(n*22)-10
1090 PAPER #n,0
1100 INK #n,4
1110 CSIZE #n,0,0
1120 CLS #n
1130 NEXT n
1140 REM old command was: WINDOW #3,40,70,350,20: CSIZE #3,0,0
1150 END DEFine
1200 REMark *****************************
1210 DEFine PROCedure udgs
1220 REMark *****************************
1230 LOCal a,n,byte,udg_start
1240 a= RESPR(0):IF a>261900 THEN a=RESPR (244)
1250 udg_start=261900
1260 POKE_L (PEEK_L (166764)+46), udg_start
1270 RESTORE 1080
1280 FOR n=0 TO 100
1290 READ byte: POKE (udg_start+n),byte
1300 END FOR n
1310 DATA 127: REM 1 less than CODE of first UDG
1320 DATA 10: REM number of UDGs defined (was 15 in the original - even though only 10 characters were defined)
1330 DATA 124,0,124,0,124,0,124,0,124: REM failure character
1340 DATA 0,32,60,60,60,32,32,32,32: REM CHR$(128)
1350 DATA 56,56,56,56,124,124,124,124,124: REM CHR$(129)
1360 DATA 0,0,4,12,28,60,124,124,124: REM CHR$(130)
1370 DATA 124,124,124,124,124,124,124,124,124: REM CHR$(131)
1380 DATA 0,0,64,96,112,120,124,124,124: REM CHR$(132)
1390 DATA 12,12,24,48,96,96,96,96,112: REM CHR$(133)
1400 DATA 96,96,48,24,12,12,12,12,28: REM CHR$(134)
1410 DATA 124,56,56,16,0,0,0,0,0: REM CHR$(135)
1420 DATA 124,124,56,56,56,56,16,0,0: REM CHR$(136)
1430 DATA 124,124,56,56,56,56,56,16,0: REM CHR$(137)
1990 END DEFine
2000 REMark *****************************
2010 DEFine PROCedure test (w)
2020 REMark *****************************
2025 LOC n
2030 AT #w,0,0: PRINT #w,"TEST WINDOW #";w
2040 FOR n=128 TO 190
2050 AT #w,0,(n-112): PRINT #w; CHR$(n);
2060 NEXT n
2070 END DEF
1240 a= RESPR(0):IF a>261900 THEN a=RESPR (244)
1250 udg_start=261900
1260 POKE_L (PEEK_L (166764)+46), udg_start
I don't understand the function RESPR particularly well (yet), but it seems that it's the QL's equivalent of the CLEAR command on the Spectrum, just not used in a way that's obvious. When defining a new character set on the Spectrum, if required 768 bytes are to be poked at 64512 onwards, the program will start with CLEAR 64511 - hence, reserving an area of memory (usually for machine code - character sets are treated the same way) that is off-limits to any BASIC program or variables.
But then, 1260 reveals an equivalent to what I know on the Spectrum. And that is, for those who aren't up to speed on that: POKE USR "a",(value) pokes the first byte of the graphics character "A" (CHR$ 144), POKE USR "a"+1,(value) pokes the second byte, and so on and so on up to POKE USR "u"+7, which is the highest address it's possible to POKE. Hence, the only difference is where USR "a" is between a 16K Spectrum and a 48K-or-more Spectrum - 32600 on a 16K model and 65368 on a 48K-and-upwards model. In effect, it's a convenient shorthand so that the same program could be used on both models in the early days (provided it would fit into the 16K's tiny memory).
The QL, by the looks of things, has no such shorthand, and the QL's equivalent of USR "a" isn't fixed, so it has to be calculated manually every time. Once this is known, the routine from lines 1280-1300 pokes the UDG values in much the same way as they'd be done on a Spectrum, just that there's none of them per character and they're all values 0-31 multiplied by 4.
What I need to discover is:
- What is being PEEKed in line 1260, why is it being PEEKed, why is it specifically 46 that is being added to it, and why does udg_start have to be POKEd to this address?
- Why do the values used by RESPR change between programs (I saw another one of David Nowotnik's programs that uses RESPR(1024) here), and what do they need to be in any specific case?
What this should lead to is a general version of lines 1240 to 1260 that can be applied to any QL program - i.e. if the channel number is known, and the CODE of the first character defined is known, then the value udg_start can always be calculated for those values, and UDGs can be POKEd to any one of the foreign-and-custom characters, on any channel, as far as the QL's channel numbers will reach.
BIG EDIT:
I immediately searched for "QL system variables", and found the Technical Manual on Dilwyn's site. This explains something:
If I type in PRINT PEEK_L(166764) after running the program I quoted above, it returns the value 168768. Add this to it and we get 168814. Hence, the POKE we are making is POKE_L 168832,261900 (where 261900 is the calculated udg_start).2.1.2 System Variables -
The Qdos system variables are a block of memory containing information required by the operating system. This block is normally located at address $28000, but is not fixed at this address in principle. Applications programs should not rely on that fixed address, but should get the address of the base of system variables by calling the MT.INF trap (see section 13.0).
Does the value 166764 mean anything to anyone, the way 23607 means something to me? On a Spectrum, 23607 is the high byte of the two-byte system variable CHARS, which by default is set to 60, revealing the character set in ROM. (The low byte at 23606 is set to 0). Hence, POKE 23607,(value) is how the entire character set is changed - define it somewhere else, i.e. above RAMTOP, then just POKE a different value of CHARS - limiting the start address of the first byte to one exactly divisible by 256 means there's no need to POKE 23606 as well as 23607. Technically, then, the address of CHARS is 23606, but 23607 is the only one of th two bytes that usually needs to be considered.
Is 166764 some kind of pointer? If I PEEK_L 166764, as in the above program, does this reveal the location of the QL's equivalent of CHARS for channel #3 - or is the channel number covered by the value 46 in the POKE, somehow?
The truth is out there.