INTRO
After releasing the ZX81 it was stated that it was a textonly computer with a predefined characterset in the ROM. Soon followed by the first games that used pseudohires graphics that gave some big eyes to everyone who saw that for the first time.
Pseudohires is bounded by the same rules as the normal characterset. Bit 6 is used in a tricky way by the ROM to find the end of a line. Bit 7 of a character is used to invert the data of that character.
Pseudohires can therefore not access all possible combinations of data and it is bound by the variations of data stored in the ROM. Richard Taylor's book “HIGH RESOLUTION” handles pseudohires and its capabilities and restrictions.
Pseudohires and the default characterset use an indirect method to display a character.
During the display the value of the character is read and the data of that character is displayed.
The ROM and the ULA keep track of the data that must be read. What if you could make the displayroutine point to your own defined data and display that? The displayroutine was analyzed further and true hires was possible.
TRUE HIRES
True hires, unlike pseudohires, needs a simple adjustment in the RAM-pack. The refreshmethod needs to be altered to make a truehires displaymethod access the RAM-data. Modern RAM-pack are equipped with this change and can do true hires. Be sure that your RAM-pack is capable of truehires.
There are more ways to activate a truehiresdisplay. The most common method is WRX-graphics, named after the person who optimized the displaymethod over the years: Wilf Rigter.
In 2011 I decided to make a port of my 1K game SHOGUN on the ZX Spectrum to the ZX81.
I was aware of truehiresgraphics on the ZX81 and I needed those since the ZX Spectrumversion used user defined graphics and true hires would make the use of user defined graphics possible.
Wilf Rigter's webpage (1) gave me (too much) information how to make a ZX81 version of the game. Too much information since the whole technical method how the ZX81 makes the display is mentioned and how that led to the displaymethod. If you want to code a game you only need the displaymethod. On the other hand information was not mentioned why a displabuffer always had to end on a 32 bytes boundary.
When I coded my ZX81 emulator for the ZX81 around 1997 I couldn't figure out why the display of the screen made a jump to memory above 48K. I altered the ROM a bit and made my own displaymethod for the emulator. To fully understand hires on the ZX81 you need to know that the display of the screen is “executed” in the memory above 48K.
The ZX81 is a clever designed device. With minimal electronics it was possible to make a screen.
The ZX81 knew when data had to be sent to the screen when a displayline above 32K was called.
The line itself remaines 32K lower so the ZX81 knows what to do.
The optimized hires routine from Wilf Rigter is displayed here:
Explanation follows.
Code: Select all
START LD IX,HR ;simple start of the hrs mode
RET
HR LD B,7 ; delay
HR0 DJNZ HR0 ; delay
NOP ; delay
DEC B ; reset possible Z flag
LD HL,HSCRN ; your hiresscreen location
LD DE,20 ;32 bytes per line
LD B,#C0 ;192 lines per hires screen
HR1 LD A,H ;get HFILE address MSB
LD I,A ;load MSB into I register
LD A,L ;get HFILE address LSB
CALL LBUF + #8000
ADD HL,DE ;next line
DEC B ;dec line counter
JP NZ HR1 ;last line
HR2 CALL #292 ;return to application program
CALL #220 ;extra register PUSH and VSYNC
BREAK ;this code segment is optional
CALL #F46 ; check break key
LD A,#1E ; restore pattern table pointer
LD I,A
JR NC STOP ; skip the HR vector load if BREAK
LD IX,HR ; load the HR vector
STOP JP #2A4 ; return to application program
LBUF LD R,A ;load HFILE address LSB
DB 0,0,0,0 ;32 NOPs = 256 pixels
DB 0,0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0,0,0,0
RET NZ ; always returns
ORG #6000 ; example destination
HSCRN BLOCK 6144,0 ; 6K of screen
That is what is done at start. The RET will go back to BASIC, but when your code is in machinecode the rest of your code will follow.
The hardware of the ZX81 will now take over the display.
In the lowresdisplay IX would now jump to the routine to start the normal display.
Now it will jump to the routine indicated by HR.
The first part is a small delay to make the display start at the visible part of the screen.
After the delay one time initialization is done.
First we need the Z-flag reset so a DEC B is done. From the loop above B is 0 so it becomes 255 and Z-flag is reset. Is is needed later on. HL is set to the start of the screen, DE is set with the length of a line and B is set with the number of lines needed, 192 screenlines.
Next we enter a loop that is 192 times executed. For each line the display is done.
You can sent data to the screen when you make the I-register and the R-register form the address
where the data is stored. In the loop that address is held by HL so the highbyte H is copied to the I-register. The setting of the R-register is done in upper memory so first A is loaded with the lowbyte L. Tha call to the displayline is made. In uppermemory, like the lowresdisplay, opcodes with bit 6 set are executed normally. LD R,A is such an opcode, so after this code I and R form the data-address. Now 32 NOP's will only increase the R register so each data is sent to the screen. After a full line a RET NZ is executed (also bit 6 set) to return to the loop. A RET NZ is needed over a RET. The timing of the loop is exactly 207 tstates, the time needed to display the line. 1 tstate was missing so RET NZ is used to get the final tstate. During display the Z-flag is reset.
The next thing to do is to make HL point to the next line and repeat this until all lines are done.
For timing DEC B JP NZ,HR1 is used over DJNZ HR1.
After the display we need to end the display in a proper way. Also the keyboard is read during intrupt. A test for a break key is added when you want to disable HR-display and go back to lowres. The breaktest can be skipped/deleted when you want to disable breaking in.
The only thing not mentioned now is why the screen must have a 32 bytes boundary.
On each display the R-register is increased 32 times. However... the R-register can't handle the change of bit 7. The R-register must therefore during display not change from 127 to 128 (this will be 0) or from 255 to 0 (this will remain 128). The R-register will then point to the wrong data. This is why the screen must have a 32 byte boundary so R will go from 0 to 31 within that line.
It is not forbidden to set the screen on a not 32 byte boundary if you can prevent R going to 128 or 0. In 1K hires the boundary is often set to a not matching boundary.
1K HIRES
Wilf Rigter even made a routine that allowed a small hires screen on a 1K machine.
This routine became the base for my 1K ZX81 hires games, although I cut off a lot of checks.
The 1K hirescode from Wilf Rigter is hard to understand. It has a lot of checks and the filling
of the final part of the screen is done in a difficult manner. Still from this routine I was able to create my first 1K hires game
Code: Select all
; IX is set in code
LD IX,HR
; the HR routine
hr LD B,3
hr0 DJNZ hr0
LD HL,screen
LD E,bytcol
topline LD BC,#8000+lines
INC B ; always 1 topline
LD A,192-lines
SUB B
LD (notend+1),A
xmove CALL delay
hr1 CALL lbuf2
DJNZ hr1
notend LD B,0
hr2 LD A,H
LD I,A
LD A,L
CALL lbuf+#8000
ADD HL,DE
DEC C
JP NZ,hr2
hr3 CALL lbuf2
DJNZ hr3
CALL #292
CALL #220
LD IX,hr
JP #2A4
lbuf LD R,A
DEFB 00,00,00,00,00,64,64,64
DEFB 64,64,64,64,64,64,64,64
DEFB 64,64,64,64,64,64,64,64
DEFB 64,64,64,64,64,64,64,64
delay RET NC
lbuf2 LD D,3
lb2 EX (SP),HL
EX (SP),HL
DEC D
JP NZ,lb2
NOP
RET
This routine makes a 5x5 screen that can move over the screen.
Depending on your location in the maze the small screen moves over the full screen.
The 1K routine from Wilf Rigter makes a 6x8 characterscreen. The routine is written to that purpose as is my routine to write a 5x5 block to the screen. And that illustrates the problem with a 1K hires game. Each game needs its own routine to do the display. When you have a game in mind you need to out think the displaymethod before you can code a game.
LOWRES and HIRES combined
I added a combined hires- and lowresdisplay in my second game BLOCKY.
After many games I made some kind of a model that starts with a lowresdisplay followed by the hiresdisplay.
Code: Select all
; some lowres, HR must start AFTER #403F, but before #4070
hr LD HL,lowres+#8000 ; the lowres display
LD BC,#311 ; minimum needed #11
LD A,#1E
LD I,A
LD A,#Fb
CALL #2B5 ; show lowres screen
LD B,3 ; match hires display with lowres
hr00 DJNZ hr00
; the hr displayroutine
; user defined
; fixed end of HR-routine
CALL #292 ; back from intrupt
CALL #220
LD IX,hr
JP #2A4
The lowresscreen is 17+2*8=33 lines. A full screen has 193 lines in lowres, first 1 line then another 192 for 32x24 characters. To make a full screen your hiresscreen must fill 193-33 = 160 lines to make a stable screen.
When your game holds less lines you can alter the C-register and add a number of blanc lines on top
without losing any bytes to code. This explains why many 1K hires games have the screen at the bottom. The top is filled by setting C higher.
1K MEMORY
1K of memory also means that the hiresscreen can't be too big when it is 1 block of screen
like Wilf's demo or my first game.If you want to give the illusion of a full sized game
(like Blocky, Bowling, Qbert, The Edge, Asteroidbelt, Itsy Bitsy Spider and many more)
you need to write a routine that can unpack a screen while doing the display.
This also counts for smaller games that do not make a full screen but still write a larger screen then the data that is stored (Ghostbusters, Monkeyboard, Make a Shit).
In short I have 4 ways of building a screen.
1) A fixed sized screen like Wilf's demo (First game Wiwo Dido, W-rotator, Sir Clive)
2) Counting screenlined and display on right screenline (Blocky, Bowling, Carrace)
3) Double display or mirroring screenlines (3D random maze, Sokob1, 1Karat)
4) Change UDG per 8 lines and multiple linebuffers (Ghostbusters, Make a Shit, Bomber)
There are more methods but mostly it is an alteration of one of the above or a combination.
The first 3 methods do not alter data in the line to display. The screen is fully built outside the hiresroutine and the hiresroutine is only capable of displaying defined data outside the routine.
Th 4th method has only a fixed number of lines (1 to 8) to display. The lines are changed just before the display is done. Each line alters a few bytes and the bytes are repaired after display to make allow each possible screen. Most games with bytewise movement have this method of displaying.
Changing bytes cost time. The more bytes you want to change the less time you have for a display. After many games I was able to change 2 User Defined Graphics over 16 fixed graphics that
can be set on/off or even inverted. This comes at another cost. The graphics can only be 7 lines in size. The 8th line will always be an empty line. This line is used to show nothing on screen so it can repair the changes made. The screen is 7*16 bytes in memory, so it will have the same
highbyte all the time. Settin I for the right display cam be done with just 1 time setting, saving 13 tstates in the loop to do other things needed. The screen is best kept on a size of max 256 bytes.
The only reason to make the screen shorter could be that the game needs more byte.
The routine of APPLEPIE can do 16 columns display but the game was too large so 2 columns had to be taken off.
To get the most out of 1K memory you need to use compressed data on loading and routines that can be placed over the systemvariables after loading. After loading the routines are copied and the
needed buffers are formed out of the first buffer. The stack must also be kept small, a 32 bytes stack is best to use. A smaller stack can be a problem. After loading you need to start the game.
This is just 1 BASIC-line of RANDOMISE USR start. This line can be placed fully over
the systemvariables. A model to get most out of 1K is available.
48K BUG
I already mentioned that during display opcodes with bit 6 set are executed. You can use this trick
to disable the display of a character by setting LD B,B in the buffer. You can also use a (conditional)
JP to go to a routine in lower memory. This can save time in your loops and it might make your code work. Most commands with bit 6 set are just 1 byte (RET, JP (HL) ) or 2 bytes that form a shifted command (LD R,A), Some commands use more bytes. A 48K ZX81 would read the real address when a command like CP N or JP NN is executed in uppermemory. The second (or 3rd) byte is read from the real address. A JP would then end in a reset of the game. To make 1K hires games run on a 48K ZX81 a fix was needed. This 48K bug can be solved with a full copy of the game exactly 32K higher in memory. This is also solved in the model for coding 1K hires games.
Conclusion
There is no 1 fix solves all for 1K hires games, but I am willing to explain a 1K source to the bottom when mentioned in this thread.
(1)
https://8bit-museum.de/heimcomputer-2/s ... ay-system/