ZX Spectrum development - Husband Chores (in Z80 assembly language)

Husband Chores is a simulation game for the ZX Spectrum computer (8-bit, released in 1982), in which the protagonist must keep the house clean to the best of his ability. There are various things which will require your attention, including clogged toilets and full trash cans.

The cleaner (fewer outstanding chores) your house remains, the more your score will increase. Similarly, your score will decrease the more "dirty" your house becomes.

As a kid, I was fascinated by my ZX Spectrum computer, and I used BASIC to create small video games. However, I knew that almost all professional games were made using Z80 assembly language, which I tried, unsuccessfully, to learn myself. Therefore, my ZX Spectrum programming did not produce a "proper" game in the past.

Today, Husband Chores represents my return to the computer which not only introduced me to the world of video games, but also kick-started my software development career.

Play in your browser

You can play Husband Chores in your browser.


TZX tape image - use this TZX tape image to play Husband Chores in an emulator
TAP tape image - alternatively, you can also use this TAP tape image to play Husband Chores in an emulator
source code package - grab this if you would like to modify Husband Chores, or to use as a basis for your own game. Includes all tools needed to build Husband Chores. Just download, unzip, and run make.bat. Tested in Windows 7 64bit and Windows XP 32bit.

Featured in Retro Gamer issue 155

Husband Chores has been featured in the British retro gaming magazine Retro Gamer, in issue 155!

Physical release on tape

Husband Chores has been released on tape, as was common for ZX Spectrum games during the computer's prime years.

Technical details

Husband Chores was developed from scratch. My approach was to create generalized and re-usable routines (which can be found in the libzx sub-directory), with the goal that other developers find them useful, as well.

The ZX Spectrum keeps hardware complexity and price at a bare minimum, at the expense of functionality. There is no hardware support for sprites (movable bitmaps across a background), and outputting any kind of sound will block the main CPU.

As such, the more technically interesting parts of the "platform" I created were:
  • bitmap routines, including bit blitting and masking, at the pixel level; these use a background buffer to achieve movable sprites which do not erase the background behind them as they move
  • a sound player queue to which a consumer can add sounds which will be played automatically every video frame

Since any kind of sound output blocks ZX Spectrum's CPU, the sounds in Husband Chores are played using a very low duty cycle during each video frame. The result is closer to "chirps" rather than sustained sounds.

The static game background was created with ZXPaintbrush, a Paint-like program that can create ZX Spectrum images.

The magenta telephone on the bottom floor is a "tip of the hat" to Matthew Smith and his Manic Miner game, possibly the most Spectrum-esque game ever created.

Chronological screenshots of the game

These show what I've been staring at, as development progressed. They are sorted newest to oldest.
The picture above is from the finished, released game.

The image above shows the later-removed message area above the top of the house. Also, the protagonist is not yet drawn, instead shown as an animated square. The moon is thinner and uglier in this one.

In the screenshot above, the walls, pavement, dumpster, and rainbow logo at the top-right all lack detail. The doors graphics reflect how I drew them initially.

The image above is taken from before the background had been drawn. It shows my test setup for masked sprites (the squares moved to the right, pixel by pixel). It also shows my custom font output tests.

Development log

This is the development log of Husband Chores, my ZX Spectrum game written in Z80 assembly language. It was developed primarily during evenings, some longer and some shorter. Outside of this, I often spent time thinking about what to do next and how to do it.

Development was spread over almost three months.

day 1
My intention was to simply install a ZX Spectrum emulator and some games to play on my laptop, but I figured I'd try to write a few simple ZX Spectrum programs in Z80 assembly (which I've briefly seen before when working with the Dreamcast VMU).
Decided on the Pasmo assembler (not only assembles Z80 code, but also inserts Spectrum-specific BASIC loaders, packaging all up in a convenient TZX tape image) and the ZXSpin emulator (which has a built-in debugger). The ZX Spectrum boots directly into BASIC, requiring that even games written in assembler have a short loader written in BASIC. Its function is to load the next block in the program (usually an intro screen), followed by a block containing the program itself, followed by a jump to the beginning address of the program.

day 2
I discovered that Spectrum's video memory is mapped in the most awkward way I've ever seen. It is not mapped linearly; instead, each successive byte "skips" to the next 8-pixel tall line, until the top third of screen sector is rendered. It then does the same for the middle and bottom third of screen sectors.
There exist a few online examples of how to go from a screen Y coordinate to a video memory address, but I decided to write it myself. It will likely be slower, but I find it more satisfying to create the algorithm myself.
Started by putting writing bytes to the video memory at various location, to make sense of how it all works, coming up with a formula to convert an (x,y) coordinate pair to a memory address.

day 3
It's going slowly, since I'm re-acquainting myself with Z80 assembly language as I'm working on this.
I'm implementing the routine which draws a horizontal, 8-pixel long line at a specified (x,y) location. Drawing 8x8 squares will be trivial, once I can draw lines. There is added difficulty from the fact that you must write no less than a byte (representing 8 pixels), and from the fact that 8 pixel long lines can straddle two adjacent bytes.
ZX Spectrum homebrew development is something I wanted to do as a kid, but was limited to BASIC due to various reasons, such as scarcity of educational material on ZX Spectrum-specific assembly language programming.

day 4
I can now draw squares directly to video memory. As for blitting, I have three versions of how the pixels are blitted to the screen:
- OR (looks good, difficult to move sprites)
- XOR (looks bad, easy to move sprites)
- "NOT-AND" (essentially an "erase mode", might be handy if I decide to mask sprites)

day 5
Calculating video memory address of Y-coordinate line I want to draw is a heavy operation. I've computed a lookup table for each of the 192 horizontal lines on the screen. It takes up very little memory and should speed up rendering of bitmaps.
Writing to video memory directly (method #1) makes everything flicker. I was almost certain this would happen, but had to start somewhere...

day 6
Created a set of alternate bitmap drawing routines to work with a screen buffer.
This is method #2 of rendering things: at the beginning of every frame, I fast-copy the entire background buffer to the video memory, and then draw each sprite on top.
Experimented with an interrupt handler, which is where I copy the background and then draw the sprites.
Method #2 is too slow. I don't have enough time to copy the whole thing, so the sprites end up flickering again.
At least when I only draw about 10% of the background, the sprites don't flicker.

day 7
I've started getting resets, Thankfully, this was easy to fix by moving all my code above the ULA contended memory, past the 0x8000 boundary.
I've started planning method #3: use the screen buffer as a "background" buffer. As soon as sprites move, I intend to copy whatever area they used to occupy from the background buffer, and copy it to the video memory. This means that there will be significantly fewer bytes to copy.
Started documenting all my findings.
I may have to bring down the frame rate to 25 fps by using two halt instructions. I would draw half of the sprites during the first frame, and the other half during the first frame. This is insurance in case one frame is not long enough. I'm not sure the interrupt handler is needed at all, since I might have to split up work between two frames.
TODO: write routine which restores what was "behind" a sprite (probably adapt a blitting routine)
TODO: clean up files and includes
TODO: switch to RRCA and RLCA, since they're twice as fast as SRL and SLA

day 8
Finally got an 8x8 sprite moving across the screen without flicking, and preserving the background behind it! One last improvement would be to add a mask, so that the background is not visible "through" the sprite. This happens because the sprite is simply ORed onto the background currently.
Adapted the OR-blitting routine to do a NOT-AND with a mask first. The ultimate effect is sprites which are no longer "see-through". Like before, they can be moved without erasing the background. This kind of sprites are the best-looking.
Speed is currently a great concern. Drawing three sprites at the top of the screen causes flickering. This is without any game logic in; just moving sprites horizontally.
To mitigate this, there are a few options:
- tighten up rendering code
- split up rendering and gameplay over two or three video frames, separated by halt instructions
- design the game so that the action takes place at the bottom; this is because the frame lines start being "painted" on the screen from the top, making it more likely for the sprite rendering code to finish by the time they are actually "painted" on screen.

day 9
Saved some ticks in rendering code by replacing sub with dec in a few places.
Saved more ticks in rendering code by replacing a useless ld.
Moved blitting routines out into their own files, so I can find and change them more easily.
Started writing a bitmap test suite, which use all blitting routines to display some bitmaps in some expected fashion. This test suite will be useful whenever I change rendering code, such as when I try to make it faster.

day 10
Finished the bitmap test suite.
Started adding my own, custom font. Back in the day, I was mystified by commercial games which featured their own font, overriding ZX Spectrum's ROM font. As a result, I decided to learn how to accomplish this and create my own font. It was actually very easy; one of ZX Spectrum's "system variables" stores a pointer to the beginning of a 256-character ASCII set. All I had to do is change that system variable to point to an array of bitmaps I defined. Each ASCII character is 8x8 and so uses up 8 bytes.

day 11
Designed my character set (font). I only made upper case letters; they should suffice.

day 12
Since my game will likely be small, I think I can store entire screens into memory. This will allow me to create a nice "press start" screen, as well as perhaps design the in-game screen itself to be mostly static. If it's mostly static, I can store it entirely, and make small modifications to it as the game goes on.
Downloaded ZX-Paintbrush, which is a Paint-like program which can create and save ZX Spectrum-formatted full screens (256x192, 8x8 attribute areas).
Designed a quick test screen and saved it using "assembler" format, basically a bunch of DB statements.
As soon as I imported the test screen, the assembler gave me a "64kb limit exceeded" error. This was peculiar, as the screen takes up only 6912 bytes. It turned out to be a bad order of ORG directives, which took the code size above Spectrum's physical 64kb limit. I reordered an INCLUDE, which fixed it. I really have to clean up these INCLUDEd modules...
After a few modifications to the "copy_buffer" routine, I am now able to display screens exported from ZX-Paintbrush.

day 13
An idea for a game is beginning to form. A single-screen game which simulates the chores usually undertaken by a man. It's a collection of unsavoury tasks including taking out the trash, dealing with torn garbage bags, shovelling snow, cleaning toilets, etc.
The single screen game may show a cross-section of a house, where each floor is a platform. Various random-occurring events will require your attention. A snow plough snowing in your driveway could be one of them. A garbage bag taken to the curb can sometimes tear, spilling garbage on the ground.
Does the garbage bin in the kitchen accumulate garbage inside over time, or do you have to gather garbage from around your home? Still to be decided...
The player scores points, and the events happen progressively more often.
The house cross-section will be designed in ZX-Paintbrush. I may even have enough memory for an intro screen which says "Press Start". This intro screen is shown immediately after loading, and it seeds the random number sequence. It shows a man carrying a garbage bag which has just torn. Garbage is spilling out, and the man is visibly angry, and is cursing ($#!#!).
Saved several ticks by removing superfluous push/pops from the "copy background buffer" routine.

day 14
I'm moving things around, and cleaning up code a bit. I'm decently happy with the sprite routines I've written, so this is a good time to clean up.

day 15
Still moving code around, bitmaps mostly.
Wrote a "print at" routine, which displays strings on the screen.
Updated bitmap rendering code which operates on the background video buffer to no longer require the buffer to be explicitly ORGd somewhere. This removes the silly requirement that the bitmap code be included at the very end of the program.

day 16
For now the bitmap code is clean and organized enough.
Took even more code out of the now diminishing "main" file, where I prototyped all sorts of routines. This main file became a mess, and is slowly becoming manageable again.

day 17
Moved more code out of the main file.
I was thinking that it's funny how I went from just wanting to configure a Spectrum emulator to play some games to using it exclusively to figure out Z80 assembler, ZX Spectrum architecture, and working towards writing a game. Even funnier is that I have not yet started a real game in the emulator, only my own code...
Of course, all I have so far are infrastructure routines. The game will be coming up soon.
Got a more complete understanding of the interrupt-to-handler workflow.

day 18
Factored out the dirty keyboard routine I had, into its own file. It trades performance for ease of use in that it scans the entire keyboard, providing a few variables to AND with key constants in order to test for keys.

day 19
Completed the keyboard key definitions. They are used when testing various keys.
I now have a large sprite (made of two tall bitmaps) that I can move left and right.

day 20
My keyboard-moved sprite is now animated (two animation frames), and changes graphics based on direction (left-facing and right-facing). It is slowly becoming my protagonist.
Wrote a rectangle collision check routine.
Started investigating the jsspeccy JavaScript ZX Spectrum emulator. It could be useful if I decide to host my game so that it can be played directly in the browser.

day 21
I've begun drawing the background. Not only is pixel art very time consuming, but the 32x24 attribute plane restriction makes surface contact areas especially tricky to design.
The house looks OK so far, but needs some variety in the walls (cracks, etc.), and some snow piled up near the tree. The pavement could use some cracks as well.
The inside of the house needs doors, paintings and other decorative objects.
I have to figure out where to show the item being held.

day 22
Wrote a routine which sets attributes directly to the video ram.
Investigated how to stream other control characters to ROM's print routine. It turnes out to be as easy as with the "AT" parameter which specifies the position.

day 23
Wrote a routine which sets attributes for a number of contiguous 8x8 pixel squares. This can be used immediately after printing text to the screen, to set the text's ink/paper/bright/flash attributes.
Improved the background image a bit.

day 24
More design of the background image. The background will be largely static, with very few modifications during game play.
My graphic design skills are almost non-existent, causing me to progress very slowly at anything involving art.
ZX Spectrum's 8x8 attribute squares make it very difficult to design contact surfaces between irregular shapes of different colours. For example, the hardest such contact surface to design was between the jagged tree and the slanted roof.
Since there will be some colour clash inside the house (as the protagonist walks in front of doors), I chose to design the static parts to have absolutely no colour clash.
The protagonist will be drawn in bright white ink. This means that all other graphics in front of which the protagonist can walk should also have their bright bit set.
The upper floors will be reached via doors. The doors open and close periodically. The top floor has a raised ceiling on the left-hand side, where I'll draw a nice chandelier.
Doors have to be at least 3 squares away from each other, so that the protagonist can only be colliding with at most a door at any given time.
The attic is where the tools can be found. The tools could be a plunger and a snow shovel. Taking the trash out will not require a tool.

day 25
More background image work. Added gauges, a nice chandelier upstairs, and doors.
It looks like I will draw the sprite every other frame. I have to split up the code so that first frame renders the protagonist, and the second performs all game logic.

day 26
Factored out a bunch of stuff from the "main" file.
Changed the input-then-render code to work more like traditional sprites. The game will run at 25 frames per second, by dedicating one frame to rendering, and the other to game logic.
Restructured source files into their own directory.

day 27
Started writing door routines. They will open and close the doors, redrawing as necessary. The more interesting cases involve the protagonist walking over a changing part of the background.

day 28
Hooked up the "toggle open/close" door routine up to one of the doors, periodically toggling the door every few frames.
Modified the "display man" routine to allow for the protagonist to be re-displayed when the background underneath him changes. This covers the case of a door opening or closing while colliding with the protagonist.

day 29
Made some small updates to the door toggle rendering code to only redraw the protagonist when the door collides with him.

day 30
Generalized the "door update" routine so that it can be reused for each door.
I can't update more than three doors per frame. I must space them out so that no more than one per frame will toggle.
Once the door toggling is spaced out, they work well.
I moved the keyboard handling routine in the first frame, and when the protagonist is rendered, I use up half of the frame time. This means that I can potentially take care of some game logic during the first frame, as well.
The second frame gets used up more than half when a door that overlaps the protagonist toggles. Maybe I shouldn't draw him in the door toggling frame, and just rely on the first frame to draw him.
If any of my frames take longer than 1/50s, the frame counter and door toggling frame counts become de-synchronized causing doors to potentially not toggle anymore. One way to make up for this is to always know when I'll need an extra frame, and simply decrement the frame counter manually, in order to re-synchronize it.

day 31
I'm considering the idea of a third, "auxiliary" frame, which takes care of potential events that may take place, including drawing on the screen as needed. This does bring the average FPS down from 25, but only slightly. The reason is that events happen every few seconds, which is very infrequently.
I think it will be less confusing if I explicitly increment the frame counter, given the introduction of this auxiliary frame.
Added some logic to the keyboard code to be able to support "just pressed this frame" checks, which will be used for the "action" key. Contrast with movement keys, where we check "is key pressed".
Wrote a routine which checks if the protagonist is on top of a door. If so, then he "goes through", and appears on top of the corresponding door (either above or below). Hooked this up to a "just pressed" check of the key M, which I intend to make the "action key".
Started implementing functionality for controlling the inventory, and how it is displayed via the "HELD" indicator.

day 32
Pulled initial values for a bunch of variables into initializer routines. These will also be useful when starting a new game after finishing a previous one.
Started writing inventory routines, which pick up the plunger and shovel.

day 33
Encapsulated protagonist movement functionality well enough to allow for collision detection with walls and other impassable areas, such as the large garbage dumpster, and the snow.
Factored out some protagonist collision routines.
Implemented basic collision with walls and dumpster. The protagonist now stays within the house premises. Still need a few more around the items, so that the protagonist can never overlap with toilets and trash bins (since they could be flashing when active, making him flash as well...)
Drew a bunch of decorative objects. Also put in a giant purple telephone, as a tribute to Matthew Smith's Manic Miner mutant telephones!
The house now contains a lot of wildly coloured items, properly representing the Spectrum's chromatic capabilities. Of course, this causes a lot of attribute clash, which I don't mind. I don't mind it because it's what makes ZX Spectrum games ZX Spectrum games.
Filled in all other collision checks, and now the protagonist stays within all constraints. He will need to be blocked by snow, as well, but that will come later.

day 34
Added random number generation functionality to the platform code.

day 35
Started writing the event generation routines. They are showing some deficiencies in my random numbers code in that the distribution is very biased towards certain values.

day 36
Fiddled with the random number routine a bit, to get more evenly distributed (for what I need) values.
Minor improvements to events code.

day 37
Implemented the animation of the warning signs above toilets, trash bins, etc. I initially tried to just set the flash attribute, and letting the hardware take care of the flashing. However, it ended up looking ugly, so I'll animate my own warning signs.
Animation frame 2 is getting a bit busy, with about 75% of processor time taken up. I have to take care and stay clear of 100% to prevent animations from desynchronizing.
Wrote the necessary routines to "turn off" events.

day 38
When a door toggles, the protagonist is no longer re-drawn in the same frame. Instead, his re-display is scheduled for frame 1 (which is when he gets rendered anyway). This brought frame 2 down from 75% CPU time usage to only 25% for frames when a door toggles behind the protagonist.
Started writing the routines which deactivate the events when the player is nearby and the action key is pressed. Such actions include picking up the trash from a trash bin that's full, unclogging a toilet, etc.
Fixed a copy/pasta bug where picking up the shovel actually counted as picking up the plunger. I'd like to see someone trying to unclog a toilet with a shovel.

day 39
Added the routine to handle garbage being dumped.
Refactored try-semantics functions throughout to make interesting code areas easier to read, for a small performance cost.
Started working on a nice multicolour border effect. This kind of effect relies on precise timing between switching the border colour several times within the same 20 millisecond frame. I think I will use it for the static main menu.
Designed the garbage bag icon, and drew it on the dumpster as well, to make it more clear that the dumpster is the destination of garbage.
Started implementing routines to help with the creation of the menu screen.

day 40
Wrote a routine to clear the video ram data bytes and fill it with a specified attribute.
More work on the main menu.
Designed digits 0-9 in my custom font.

day 41
More work on the main menu to show the items relevant to the game rules.

day 42
The menu is now in good shape. I have added all elements I wanted: title, border effect, goal of the game, explanations for each event, keys, and "press to start".
Re-did the rainbow at the top right of the screen to look much nicer.

day 43
Wrote a nice colour-cycling routine for the game's title which also cycles the bright bit. Basically, it cycles like so: blue -> bright blue -> red -> bright red -> etc.
Unless I decide to add any music, or sound, the menu is now complete.

day 44
I am now writing the routines which control the snow pile which can appear in the doorway, along with the snow plow which packed it in. The snow plow will be visible for a few seconds (aesthetic only), after which it goes away.
The snow introduces an irregularity in that the player cannot be holding the garbage bag when the snow appears. This is because garbage can only be dropped in the dumpster, which is now blocked off by the snow. This also means that when the snow is present, the player cannot pick up any garbage from the trash cans.
The protagonist now collides with the snow properly.
Designed the snow plow graphics. It even has a flashing blue light on top!
It takes a good chunk of a frame's CPU time to draw or erase it. As such, I've designed it so that the snow plow is erased before the player can reach the snow with the shovel. The result is that frame 1 (the protagonist-drawing frame) remains unburdened by having to erase the snow plow; it only needs to erase the snow pile, which is much faster.
Snow can now be cleared when holding the shovel.

day 45
Did some more work on the background image. It includes a nice tall plant underneath the chandelier, a fancier giant clock, and a recoloured roof.
Started writing the routines which handle the protagonist dropping his garbage bag (it breaks) on his way to the dumpster.
TODO: Drop garbage in front of protagonist (must take his facing direction into account)
TODO: The random number generator is still subpar, and I don't like it

day 46
Garbage is now always dropped in front of the protagonist, regardless of his orientation.
Modified the walking animation code to be based on four animation frames rather than two. Once the graphics for the walking protagonist are in, four frames will make it a look a lot better than two. My graphic/animation skills are very poor, so I usually leave the main character design until the end. Just like a university student tidying his room thoroughly before exams, I am somewhat apprehensive of the animation I must design.
Wrote the "delay due to work" code. It has no "working" graphics yet, but it was a decently big step. The protagonist now looks like he's actually doing something as he's unclogging toilets or picking up garbage.

day 47
Did a first pass of adjustments to event/snow/trash generation rates (frequency of die rolls and die roll chance) and actually played the game like a game for the first time.
Dropping trash currently has no "frame count cooldown". This means that the die is rolled every frame, causing the protagonist to drop the garbage multiple times in a row when standing still after he initially drops it.
TODO: Decide whether to disallow dropping the garbage again if player hasn't yet moved from him previous position.
TODO: Consider whether the menu needs something like "take care of snow immediately" vs. letting the gamer figure it out himself

day 48
I think the random number generator might be ok. When you add everything in, it does seem "random enough".
More play testing today, to get a feel for how far I am from good enough die roll frequency and die roll chances.
I finally mustered enough courage and drew the animation frames for the protagonist. Through some sort of luck, or mystical planetary alignment, it turned out very good the first time. He looks a bit pudgy and is largely invisible while overlapping a closed door.
TODO: Make protagonist more visible while overlapping a closed door. Options include an outline via the mask and texturing the closed door.

day 49
The protagonist is now visible when overlapping a closed door, due to changes in the texture of the doors.
Made a small change to the menu screen, to indicate that snow must be cleared immediately. The reason is that trash cannot be taken out when the snow pile is present.
Fixed a bug whereby upon finishing working, the protagonist was not properly erased before being re-displayed in his "walking" pose.
Added half of the "working" animation.

day 50
Removed the message area completely. The rules of the game are simple enough, and well-presented on the menu screen to not need to overcrowd the game screen with messages.
As a result, I gained some space at the top, and re-drew the moon to be chunkier and nicer.
Finished the protagonist's "working" animation.
Started writing the score gauge routines, beginning with one which fills up the gauge in accordance with the current score.

day 51
More work on the gauge and scoring routines. I am lucky that frame 2 was very light on the CPU, which allowed me to re-display the entire score gauge every time the score changes. The advantage of this approach is lower code complexity around gauge updating. Frame 2 uses at most 60% of CPU time in the worst case, which is when a door toggles, and the score is re-calculated in the same frame.
I made a mistake. I forgot about the snow plow, which adds a considerable amount to the CPU usage in frame 2. Therefore, I moved score updates to frame 1, which was actually lighter in the worst case than frame 2.
Summary of CPU usage per frame so far:
Frame 1: 80% in the worse case (protagonist re-displayed not on a 8-pixel boundary and score is updated)
Frame 2: 80% in the worst case (a door toggles and the snow plow appears)
The gauge rendering routine now works fully.
The scoring routine also works fully, although the game is far from balanced.
Wrote the loss and victory checking routines.
TODO: Add a hard "if no events for a long time, add one" check. I like the streakiness of the random number generator when it makes events, but don't like it when the player just stands there with no events active.

day 52
Implemented the "game won" info screen. Factored out the routine which makes a multi-colour border and waits for a key press. This is to re-use it in three different places.
Implemented a NO-OP mode in the interrupt handler. The reason is to have a mode in which the CPU time taken by the handler never changes. Such a mode will be used on info screens to not disturb the timing of the multi-colour effect, following right after the interrupt handler returns.

day 53
Implemented the "game lost" info screen.
Added a delay right when the game is won or lost, to not surprise the player by immediately switching to the info screen.
TODO: Same as before, force event generation after enough seconds without an event.

day 54
After enough time elapses without an event being generated, a generation is forced.

day 55
Improved random number generator by also basing it on a sequence of bytes from ROM, whose values are "pretty random".
Aside from sprites and pixel-level manipulation and blitting, I have now reached what I consider the second very interesting topic in ZX Spectrum game development: sound. Sound on the ZX Spectrum is generated through a simple, single channel, speaker driven by the CPU. This means that the CPU blocks when playing a sound. If game play is not to be interrupted, CPU time slices of each video frame must be allocated to outputting audio.
I am experimenting with outputting sound from the interrupt handler. The advantage is that it happens "in the background", resulting in clear code. The disadvantage is that it delays rendering the protagonist, causing flicker if any significant chunk of the video frame is spent playing the sound.
I may have to explicitly call the "continue playing sound" routine manually at the end of each video frame, so that it happens AFTER protagonist has been displayed, removing any possible flickering.
I will ultimately package the platform routines as a library, complete with examples.
TODO: Add "was key just pressed?" to the keyboard routines.

day 56
Moving the call to the "continue playing sound" routine to the end of each frame (after game logic) revealed a problem with my plan; game logic duration can vary from frame to frame, causing the sound to be heard at different times within consecutive frames. The result was a distorted sound effect output, giving the impression of lag.
Implemented the sound playback infrastructure. It is based on a circular queue to which a consumer can add sounds, specifying pitch, time slice duration, and total duration in frames. The consumer then calls "continue playing sound" on every frame, which progressively clears the queue.
The result is playback of "continuous" sound over multiple video frames, without any game play slowdown or freezing.
All these sound experiments I've been making have made me understand why ZX Spectrum games sound the way they do. It's because their sound output duty cycles must be similar to my own.
Added "was key just pressed?" to the platform keyboard routines. Switched all such checks to use the platform calls.
Cleaned up some no longer needed code and renamed a few files.

day 57
Somewhat similar to the sound effects routines, I implemented music playback routines which loop through a consumer-specified buffer of sound entries.
Fixed a bug whereby the music routines weren't looping properly.
Began writing sound manager routines, which can play back both effects and music, mixing them in several modes.

day 58
Implemented three playback modes in the sound manager. This needed some modifications to the lower-level sound and music routines.
Added pass-through routines to the sound manager; they pass through to the effects and music routines. Their purpose is to consolidate all sound functionality into a single file.

day 59
Modified sound playing routines to consider a pitch of 0 as silence.
I've started implementing sounds for various actions.
Added a way to lock the sound effects queue, for long-playing sounds like the snow plow beeping, in order to keep more frequently-occurring sounds from interrupting them.

day 60
I'm doing a lot of play testing today. Most of the difficulty adjustments are downwards, with lowered chances for the snow plow and events to take place.
I have named my ZX Spectrum homebrew games library "libzx".
Added comment headers to all source files, and commented the entry point.

day 61
Moved main game sound playing to be called from the interrupt handler again.
Changed the sound effects playback routine to register a "silence" (pitch 0) sound as having actually output the sound. This makes it so that even "silence" entries in the effects queue will interrupt music playback. This makes "interrupted music" playback mode much more pleasant to the ear.
Modified the info screen "wait for keypress" routine to also play music.
Added a sound, confirming keypresses on info screens.
Added "game lost" and "game won" sounds.

day 62
Made the warning signs above full trash cans turn red when snow is present, to differentiate from the normal yellow warnings signs when trash CAN be picked up.
Introduced a warning sign above the snow pile as well, to better draw the player's attention to it, just like for toilets and trash cans.
Each bag of trash can now only be dropped once on the ground.
Restructured most directories to clean up the project root directory.
Switched unnecessary "set many colours" to "set colour" calls.

day 63
Cleaned up make script. It now also generates a TAP image, alongside the TZX image it's been generating all along.

day 64
I'm playing through the game multiple times, to get a better feel for the difficulty. I think that the difficulty is at a satisfactory point, where even I lose sometimes.
I've adjusted the very short "music" loop that will play throughout. The sounds are spaced apart, and are very "lo-fi", due to not being able to spend too much of the CPU on sounds each frame.
Packaged tape images.