This is an implementation of a limited subset of OGL-SRD 5.1 for the Tandy TRS-80 Model 100 and ZX Spectrum.
This project requires zasm
and python 3
. The build system is waf
, which itself is built on Python, and the waf
"executable" is versioned alongside this project. To use waf
, you can invoke it with ./waf
on *nix platforms, or use ./waf.bat
on Windows. It's recommended, especially if you use other waf
projects, to set up an alias to make invocation simpler (such as alias waf=./waf
).
When you first check the project out (or after pulling down updates), you'll first want to run waf configure
, which will set up some paths and ensure it can find zasm
. This step will fail if zasm
is not in your PATH
, or if your python version is below 3.
After that, simply run waf
to build all outputs and run unit tests. You'll usually only need to run waf configure
after changes to the build script, or to make changes to your selected options.
The ZX Spectrum build is currently disabled by default until stabilized. To enable it, pass zx_spectrum
to waf configure
's --platform
option. You can supply it on its own, or with both platforms:
# Spectrum on its own
waf configure --platforms=zx_spectrum
# Spectrum and Model 100
waf configure --platforms=trs80_m100,zx_spectrum
When you select the zx_spectrum
platform, waf configure
will also ensure you have bin2tap
(from zxspectrum-utils
) available in your path. This is required to package the raw binary output from zasm
into a tape format with a BASIC
bootloader.
Note: The reason this is disabled by default is that the build will currently fail on the first pass when calling
bin2tap
. Simply invokingwaf
multiple times does eventually produce a workingtap
file.
DDE is designed for systems with at least 24k of RAM (i.e, 21446
Bytes Free upon cold boot).
DDE is designed for systems with at least 48k of RAM.
Built into this project is a test campaign that flexes features of the engine. Other projects that wish to use this engine should be able to utilize this engine to create bigger experiences.
The build process creates raw binaries in two formats for the "Test Campaign," which should be output at build/trs80_m100/test_campaign.co
and build/trs80_m100/test_campaign.hex
. The former is a Model 100 .co
-format machine language program file, and the .hex
file is an Intel Hex format encoding of the raw campaign binary.
Note: So far, both the large flagship project and even the small test campaign are already too large for the
.co
file to be saved to the Model 100's internal storage.
With help from majick, the build script produces a .co
-formatted file binary file. This file is too large to work in the Model 100's built-in storage, but can be loaded up through the cassette interface. This is currently only confirmed working on CloudT, but should, theoretically, work on a stock Model 100 when loaded through the cassette interface as well. To run it on CloudT:
clear 256,43776
test_campaign.co
cloadm
call 43776
If you have a serial connection established with a PC running an application that can send ascii files, this repository includes a two-step process to transfer the campaign binary to it. For the first step, run clear 256, 43264
, as we'll be using some higher memory than the campaign itself. Transfer the loadhx.ba
BASIC program under utils
to your machine and start it up. It will await an intel hex format file and begin poking it into $A900
(43264
). Send over build/trs80_m100/ldhx.hex
. Note that this first loader script is slow, and has only been proven to work consistently through Tera Term
with a 50ms delay between characters. It will only be used to load the faster loader. When it is complete, an assembly-language version of essentially this same application will be loaded, and you can save it for easier re-use now with savem "ldhx",43264,43674,43264
. Before saving, you'll want to delete the original loadhx.do
file to make some room.
Once the fast loader is loaded, running it will once again wait for an intel hex format file, only now it will begin inserting at $AB00
(43776
). This loader is much faster, and has proven stable with only a 5ms delay between characters. Once it's done loading, start it up with call 43776
.
Note:: Both loaders hardcode the serial port
STAT
value to88N1E
. Ensure your serial terminal is configured appropriately.
Using the Virtual-T emulator, first run clear 256,43776
. Then, using the Memory Editor
tool, load the output .hex
file starting at address $AB00
. Once that is done, you can run it with call 43776
.
If configured to build for the Spectrum, a tap
file will be available at build/zx_spectrum/test_campaign.tap
. You can load it like any other tape with LOAD ""
. This has so far only been verified via the Fuze
emulator set to the Spectrum 48k
ROM, and has not been verified on physical hardware.
DDE follows a typical RPG setup with screens for exploration, combat, and dialog interactions. It has a handful of simplifications from the full OGL 5.1 experience. These may eventually be expanded on, but not with any current concrete plans:
On all platforms, both arrow keys and WASD
are supported for movement and navigating menus. ENTER
is used to select menu options and interact with interactables. On the Model 100, the ESC
key can be used to bring up the menu and cancel combat attacks and casting. That key is DELETE
(backspace) for the ZX Spectrum.
The E
in DDE
stands for Engine
because it is structured as a set of modules designed to present gameplay components to progress through campaigns designed for SRD 5.1 (though many features will be left unimplemented). This project's top-level entry points are under src/apps
, and they include src/engine/dde.asm
. This means everything within DDE
itself could end up located anywhere once assembled, depending on the campaign. Currently, the two apps are the unit tests and a test campaign. This may expand to include a faster version of the hex loader in assembly, or a more-substantial example campaign.
As mentioned, other projects that wish to use this engine should be able to simply include this project (submodule, etc.), then #include "(dde root)/src/engine/dde.asm"
in their source, and compile their root file with zasm
and the --8080
flag.
Even though this is for an 8085 CPU, It's using Z80 Assembler syntax. This is a feature of zasm
, and additional Z80 instructions not available in i8080 are disabled via the --8080
flag. This is encouraged by zasm
as i8080 mnemonics are harder to read, and this author already has some familiarity with Z80 syntax as well.
The user interface is divided into three levels:
Note that a "Component" in this context is a configurable singular UI (for example, menu
allows selection from a list of options all shown on one screen), rather than a sub-screen element that would share the screen with other components.
All three levels are implemented as subroutines with the same calling convention as the Model 100 ROM routines, where arguments and return values (or pointers thereto) are passed in registers. For example, the Character Wizard accepts a pointer to character data that will be filled in completely by the time it exits. The individual screens' function interfaces each set up their data by exit, too, which is wrangled into place by the Wizard between steps. Further down, each screen of that UI may or may not defer some of its work to a common Component.
Since each screen does so much, you may generally expect all registers to be destroyed or set to an exit condition with each call. Also of note is that all screens are currently static, rather than using stack space for their local data. This may change eventually, but, for now, as a general rule, UI pages should not nest calls to one another. It should technically be fine for unrelated screens to nest calls, but a screen can't, for example, use a menu
and nest a call to another screen that uses a menu
, since the second would overwrite the first's workspace.
The subroutines in screen_controller
allow for simple setup of the exploration/combat loop. By registering "room" and "encounter" tables and the player party, one can create subroutines that wrap the exploration_ui
and combat_ui
. The combat UI will always exit to the last room, and the exploration UI can exit to either another exploration wrapper or combat wrapper by setting last_screen_exit_code
and last_screen_exit_argument
before exiting. See apps/test_campaign/screens
for examples.
The screen table and boilerplate for screens and basic interactions is generated from JSON via tools/screen_generator
. Check apps/test_campaign/dde_project.json
for examples. Each screen is listed in the dde_project.json
file, along with its background data and "interactables." An assembly file is generated for each one, and you can provide an optional custom assembly file under screens/<name>.asm
for any extra needed functionality. Everything in the additional file will be with a .local
scope to the generated file.
Screen backgrounds (and eventually more) can be edited via the Python application at tools/editor
. For example:
python tools/editor ./src/apps/test_campaign/dde_project.json
The screen editor uses tkinter
, which is included with Python by default on Windows, but needs to be installed on other platforms.
A simple string compression algorithm allows for apps to store their strings in a JSON file and run it through tools/compressor
to generate an assembly file where all strings are declared, but up to 126 of the most-common sequences of characters will be extracted to the top, and a single-byte reference to each sequence in the lookup table is used as a byte in each original string with its MSB flagged. To print them, use print_compressed_string
, PRINT_COMPRESSED_AT_LOCATION
, or BLOCK_PRINT
. Note that compressible text may not contain characters above value 126
(~
), though that range does include most typeable, non-graphical characters. Make sure to include the generated output file somewhere in your source code.
If your app doesn't need to compress its text, it should still run tools/compressor
without the -i
argument to get the compressed core engine text and include the generated file.
Tests are located in src/apps/tests/main.asm
, and are compiled and run automatically by zasm
when the file is assembled.