SkoolKit

Spectrum game disassembly toolkit

No rest for the simulated

SkoolKit 8.10 has been released. To get a copy, please head over to the download page, the Python Package Index, or GitHub.

As the title suggests, this release of SkoolKit is (again) almost entirely about Z80 instruction set simulation. And to rub salt in the wounds of those who still have no interest in the subject, it’s specifically about Z80 instruction set simulation in the service of LOADing tapes. To my recollection, never has a single SkoolKit command received so much attention over a sequence of releases as tap2sna.py has since 8.7 came out in October last year. So, to the fans of tap2sna.py that are still reading: strap in.

First up is the finish-tape simulated LOAD configuration parameter, which (when set to 1) forces tap2sna.py to play the tape to the end before stopping the simulation at the given start address. This is particularly useful for custom loaders that hit the start address more than once during the loading process, and also for stopping purely ROM-based loaders at $053F (SA/LD-RET) after the last block on the tape has loaded.

Next is the contended-in configuration parameter, which (when set to 1) makes tap2sna.py interpret ‘IN A,($FE)’ instructions in the address range $4000-$7FFF (i.e. contended memory on a standard 48K Spectrum) as reading the tape. Lest you think this couldn’t possibly be useful, it’s actually required by such games as Fly Swatter, Removal Deluxe and Sapper, all of which have custom loading routines located in the aforementioned address range.

After that, there are 32 new tape-sampling loop accelerators in this release, brought in to accompany the original 11. And although the casual user will rarely need to think about them, if you are curious which accelerators (if any) a game uses, you can now set the accelerator simulated LOAD configuration parameter to list, and tap2sna.py will do just that:

$ tap2sna.py --sim-load -c accelerator=list GYROSCP2.TZX gyroscope2.z80
Program: GYROSC.II
Fast loading data block: 23755,1250
Data (602 bytes)
Data (46414 bytes)
Tape finished
Simulation stopped (end of tape): PC=64809
Accelerators: rom: 12857; speedlock: 113290; bleepload: 632000; misses: 25/62
Writing gyroscope2.z80

The Accelerators line here shows that a rom-style tape-sampling loop was entered 12857 times, a speedlock-style loop 113290 times, and a bleepload-style loop 632000 times. In addition, the tape-sampling loop detectors, which are triggered by ‘INC B’ and ‘DEC B’ instructions, counted 25 misses for ‘INC B’ (i.e. 25 instances of ‘INC B’ not inside a recognised tape-sampling loop) and 62 misses for ‘DEC B’.

For those even more curious about custom loaders, the format of the trace log file produced by tap2sna.py during a simulated LOAD can now be defined via the TraceLine and TraceOperand configuration parameters. In addition to the current address and instruction, you can log the current value of any register, and also the current time (in T-states) as recorded by the simulator’s internal clock. In conjunction with the new --tape-analysis option of tap2sna.py, which provides an analysis (including timestamps) of a tape’s tones, pulse sequences and data blocks, this enables you to figure out what’s being loaded and where at any point in the loading routine. If that’s your kind of thing.

But if all of this so far seems like too much information, and all you want is for --sim-load to just work for a particular tape, you could do worse than consult the t2sfiles repository. It contains ready-made ‘t2s’ (tap2sna.py argument) files for thousands of games, with appropriate start addresses already figured out, and appropriate accelerators already specified.

And on that note, I shall direct any readers who want even more information on the goodies (simulation-related and otherwise) inside SkoolKit 8.10 to the changelog. When you’re done there, please download a copy of 8.10, and perhaps a copy of the t2sfiles repository, and give --sim-load a good workout. For real this time.

LOADs more simulation

SkoolKit 8.9 has been released. To get a copy, please head over to the download page, the Python Package Index, or GitHub.

I am aware that Z80 instruction set simulation has been a recurring theme here lately, so let me apologise up front to readers who have no interest in the topic that this article continues - out of necessity - with the simulation motif. Perhaps it will have faded by the time 8.10 arrives; we shall see.

So with that apology out of the way, let’s get started. First, the good old --sim-load option of tap2sna.py. Not content with the fourfold increase in its performance from 8.7 to 8.8, I have added support for accelerating the tape-sampling loop of the most common loading routines out there: Speedlock, BleepLoad, Alkatraz, to name but three. To give an idea of the improvement in simulated LOAD times that this feature brings, here are some examples showing the performance of 8.8 and 8.9 on my computer using Python 3.11:

  • 0m25s (8.8) v. 0m16s (8.9) - Fairlight (Alkatraz)
  • 0m49s (8.8) v. 0m12s (8.9) - Black Lamp (BleepLoad)
  • 0m35s (8.8) v. 0m08s (8.9) - Satan (Dinaload)
  • 0m35s (8.8) v. 0m17s (8.9) - Skool Daze (Microsphere loader)

tap2sna.py in 8.9 also comes with improved support for TZX files, specifically for loops, pauses, and unused bits in data blocks. This along with the new --tape-start and --tape-stop options (for snipping redundant parts off each end of a tape) means that --sim-load now works with more games than ever before. In fact, in a recent test of over 6800 TAP and TZX files that the Fuse emulator is able to LOAD correctly, --sim-load produced a working snapshot from all but three of them:

  • 1999 - the TZX file fails to LOAD, but the TAP file LOADs successfully; interestingly, I have a copy of an older TZX file named ‘1999 (Alternative).tzx’ that has different pulse timings and LOADs perfectly
  • Blood Brothers - the TZX files for the Spanish releases of this game fail to LOAD, but the TZX file for the Gremlin Graphics release LOADs successfully
  • Gold Mine - this one fails because the loader depends on timing-accurate simulation of code running in contended memory, which is not supported at the moment (and may never be)

In other simulation-related news, trace.py now sports an --interrupts option, which switches on the execution of interrupt routines. This new feature enables us to answer such important questions as: What does the screen look like after Skool Daze (whose main loop depends on interrupts being enabled) has loaded and 10 million instructions have been executed without any keyboard input?

$ tap2sna.py --sim-load --start 24288 skooldaze.tzx sd.z80
$ trace.py -i --max-operations 10000000 --dump sd-10m.z80 sd.z80
$ sna2img.py -s 2 sd-10m.z80 sd-10m.png

So now we know. And now you know all the good stuff about SkoolKit 8.9. There were a few other changes and several bug fixes since 8.8 that are not detailed here, but if you wish you can consult the changelog for information on those. Let me keep you no longer from downloading a copy of 8.9 and converting your collection of 48K TAP/TZX files into pristine snapshots.

Oversimulated

SkoolKit 8.8 has been released. To get a copy, please head over to the download page, the Python Package Index, the Ubuntu PPA, or the Fedora copr repo.

Arriving hot on the heels of SkoolKit 8.7, 8.8 is a minor update with just a few (but important) changes, chief among them being an improvement in the performance of the Z80 instruction set simulator. (Yes, strap in for some more simulator-related discussion.)

Now, the simulator’s performance (i.e. speed) does depend to some extent on what the code it is simulating actually does, so allow me to give some concrete examples comparing 8.7 and 8.8 to illustrate the improvement. First, everyone’s favourite (OK, perhaps just my favourite) application of the simulator: tap2sna.py --sim-load (in this case operating on a TZX file for Skool Daze). Second: running the z80doc tests in Patrik Rak’s z80test suite. And third: expanding an #AUDIO macro to generate a WAV file of the Manic Miner theme tune (which runs to 20.25s). Here are the numbers showing the performance of 8.7 and 8.8 on my PC using Python 3.11:

  • 2m20s (8.7) v. 0m35s (8.8) - tap2sna.py --sim-load skooldaze.tzx sd.z80
  • 2m42s (8.7) v. 0m56s (8.8) - z80doc tests
  • 7.56s (8.7) v. 2.98s (8.8) - #AUDIO4(blue-danube.wav)(34351,34358)

Still on the subject of --sim-load, that option now also performs any call, move, poke and sysvars operations specified by the --ram option, in case you want to modify the pristine memory contents - no judgement! - before writing the Z80 snapshot.

Naturally, this improvement in simulator performance also means that the #SIM macro is now faster, and so too is the #TSTATES macro (when it uses the simulator to execute instructions, that is).

The final bit of simulator-related news is that 8.8 has a new command: trace.py. I had originally planned to squeeze this into 8.7, but it didn’t quite make it into that already jam-packed release. Anyway, trace.py uses the simulator to run machine code in a 48K memory snapshot, optionally showing the instructions executed and register values along the way. It can also compute the delays between speaker flips, in case you ever wanted to manually supply the delays parameter to an #AUDIO macro (which might be desirable if the code in question takes a long time to simulate).

Speaking of the #AUDIO macro (again), or more specifically the [AudioWriter] section that controls WAV file creation: the MaxAmplitude parameter has been removed. Its default value was 65536, and roughly speaking it determined the ‘rectangularity’ of the waveform produced when converting delays into samples. But some experiments with low-frequency sounds made it clear that a better default value is ∞ (infinity), for maximum rectangularity and therefore similarity to the sound of a real Spectrum. Leaving MaxAmplitude around just so WAV files can be made to sound less Spectrum-like seems pointless, so out it goes.

And that’s all the news. At this point I would usually direct readers to the changelog for details on any changes not described here, but this time there are no changes not described here, so, well, don’t bother. Just fire up a copy of 8.8 and get simulating.