LSpixel

Developing for the ZX Spectrum with Codex: How I Configured the AI to Work with Z88DK

Using AI to develop modern software is no longer particularly unusual. Asking it to work properly on a ZX Spectrum 48K is already a very different matter.

For some time now, I have been developing a small roguelike for the ZX Spectrum in C using Z88DK. The project is deliberately designed around the machine’s limitations: ASCII or pseudo-ASCII graphics, small data structures, limited memory, reasonably simple dungeon generation, and game logic simple enough to remain realistic on a Z80.

I use Codex to help me with the code. Very quickly, I realised that the main difficulty was not teaching it C. It was making it understand what kind of C it had to write, for which machine, using which toolchain and, above all, within which limitations.

The First Problem: Avoiding “Modern C on an Old Machine”

If you simply ask a coding agent to create a roguelike in C, it has far too broad a universe of possible solutions available to it. It may naturally turn to dynamic memory allocation, relatively large structures, unsuitable libraries, or architectures that would be perfectly reasonable on a PC but completely absurd on a Spectrum 48K.

There is another danger as well: mixing platforms. When developing retro games, many of the available examples concern the Game Boy, Amstrad CPC, C64, or various modern SDKs. Two machines equipped with a Z80, or two projects written in C, obviously do not necessarily share the same libraries or hardware constraints.

The first step was therefore to turn the characteristics of the project into explicit rules.

I created a project context file called AI_CONTEXT.md. It clearly defines the target — ZX Spectrum 48K — the C language, the use of Z88DK, and the prohibition on switching to BASIC. It also instructs the agent to produce, whenever possible, a complete and compilable main.c, and to keep the project simple enough to test in an emulator.

This file does not merely describe what the game is supposed to do. More importantly, it describes what the agent is not allowed to do.

It is therefore instructed to avoid malloc, float, recursion, large in-memory structures, and unnecessary dependencies. Static arrays and simple data types are preferred. Game logic should remain as separate as possible from display code. Finally, every significant modification must be followed by a compilation, and any errors must be fixed before development continues.

That last rule is probably one of the most important parts of the entire configuration.

Giving Codex a Real Build Command

It is not enough to tell the agent: “this project uses Z88DK.”

You need to give it exactly the same command that you actually use yourself.

In my Windows environment, the current command is:

cmd /c "call C:\Users\lspixel\Desktop\z88dk\z88dk_prompt.bat && cd /d C:\Users\lspixel\Desktop\z88dk_projects\zx_test && zcc +zx -vn -startup=1 -clib=sdcc_iy main.c -o zx_test -create-app"

And the expected output is explicitly defined:

zx_test.tap

This information appears directly in the project context.

It completely changes the way Codex works. Instead of producing code that “should probably work with Z88DK”, it now has an objective test: does main.c actually pass this build command and produce zx_test.tap?

Z88DK does indeed use zcc as its compiler front end, and the +zx target corresponds to the ZX Spectrum. The -create-app option can notably generate the TAP file required to load the program on a Spectrum or in an emulator. The official documentation itself uses commands of the form zcc +zx ... -create-app.

Our configuration also uses -clib=sdcc_iy. This variant selects the SDCC compiler integrated into the Z88DK environment together with the corresponding library variant. It is available in the official ZX Spectrum target configuration.

There is an important point worth noting here: current Z88DK documentation, for example, presents -startup=31 in its minimal ZX Spectrum tutorial, whereas my project currently uses -startup=1.

I therefore do not ask Codex to automatically replace my configuration with whatever it finds in a more recent example. The project’s own working command remains authoritative for as long as it works. Documentation is there to help understand and verify the setup, not to silently alter an environment that has already been tested.

This is a fairly important rule when using AI with older tools: an example found in documentation or a repository is not necessarily the exact configuration required by your program.

Compilation Is Part of the Reasoning Process

Codex is not only useful for writing code. In environments where it has access to the project tools, it can inspect the repository, modify files, and run development commands. This is precisely the kind of workflow Codex CLI is designed for: inspect the code, make a change, and then use local tools to verify it.

For my project, the principle is therefore very simple:

understand the current state ↓ modify a feature ↓ compile with Z88DK ↓ read the errors ↓ fix them ↓ compile again ↓ only then move on to the next step

This may seem obvious to a human developer, but it is useful to state it explicitly for the agent.

Without this instruction, an AI can easily make five or six successive changes that all seem coherent on paper. If the first one introduces an incompatibility with Z88DK, every subsequent change is then built on top of a codebase that no longer compiles.

With a short compilation loop, Z88DK effectively becomes the referee between what the AI thinks should work and what the machine actually accepts.

That is far more reliable.

Never Let the AI Invent an API

Another rule I added is particularly important when working with older machines: whenever a Z88DK function or library is uncertain, Codex must not invent it.

That may sound trivial, but the most deceptive AI-generated errors are not necessarily major logical mistakes. They can simply be very plausible function names taken from another library, another platform, or reconstructed from modern conventions.

For this project, the hierarchy is therefore clear: Z88DK documentation first, then Z88DK code and examples compatible with the ZX Spectrum, followed by the simplest possible solution whenever doubt remains.

Z88DK is specifically designed for machines based on the Z80 family and includes dedicated ZX Spectrum support. Its official documentation notably contains a getting-started guide specifically for C programming on the Spectrum.

The goal is therefore not for Codex to memorise fifteen thousand library files. The goal is for it to know when to stop when it does not know.

Teaching It What a ZX Spectrum Actually Is

The context does not stop at the compiler.

A program can compile perfectly well and still be completely unsuitable for the machine.

I therefore added constraints related to the game itself: character-based display, the player represented by @, a map sized appropriately for the Spectrum screen, short messages, clearly defined keyboard controls, and gradual feature development. The file also establishes an order of priority: level generation, movement and collision, monsters, items, combat, messages, inventory, progression and, eventually, saving.

This progression is deliberate.

With AI, it is extremely tempting to immediately ask for a complete combat system, twelve monsters, magic, an inventory, complex procedural generation, and graphical effects.

But just because an agent can produce a large amount of code quickly does not mean the machine can absorb it, or that the resulting program will remain maintainable.

On the Spectrum, I therefore chose the opposite strategy: one small feature, compile, test, then move on to the next one.

In the end, this way of working is actually quite close to traditional microcomputer development.

Project Context Is Almost as Important as the Prompt

Another interesting consequence of this experiment is that I now write far fewer enormous prompts.

A large amount of permanent information has no reason to be repeated with every request.

The target is still a ZX Spectrum 48K. The compiler is still Z88DK. I still do not want malloc. The main file is still generally main.c. The program must still compile before we continue.

These are properties of the project, not properties of whatever task happens to be carried out today.

Codex now provides an official mechanism specifically designed for this kind of information: the AGENTS.md file. Codex reads it before working on the repository, and it can contain build commands, conventions, and project-specific rules. OpenAI describes it as something similar to a README intended for agents.

My AI_CONTEXT.md already serves as detailed technical documentation. If I were formalising this configuration specifically for Codex today, I would keep that document for the longer explanations and add a much shorter AGENTS.md at the root of the repository as an entry point, for example:

Target: ZX Spectrum 48K Language: C Toolchain: Z88DK

Read AI_CONTEXT.md before modifying gameplay or architecture.

Build command: cmd /c "call C:\Users\lspixel\Desktop\z88dk\z88dk_prompt.bat && cd /d C:\Users\lspixel\Desktop\z88dk_projects\zx_test && zcc +zx -vn -startup=1 -clib=sdcc_iy main.c -o zx_test -create-app"

Expected output: zx_test.tap

Rules:

This approach also corresponds to OpenAI’s current recommendation: keep AGENTS.md relatively concise and use it for rules that should systematically apply across the repository.

AI Does Not Remove Constraints: It Forces You to Define Them Better

That is ultimately what I find most interesting about this experiment.

AI is often presented as a way to program without having to understand technical constraints. On a retro machine, I have found almost the opposite to be true.

The more constrained the platform is, the more precisely those constraints need to be defined.

You need to know that you are developing for a Spectrum 48K and not simply for “a Z80 computer”. You need to choose a toolchain. You need to know what output format you want to produce. You need to decide which resources are acceptable, which libraries are allowed, and what level of complexity is reasonable.

AI can then become extremely effective because it can take over a significant part of the mechanical work: browsing the existing code, identifying the relevant functions, applying a coherent modification, compiling, analysing an error, and correcting it.

But it works all the better when its playing field is clearly defined.

In my case, the major improvement therefore did not come from finding some “magic prompt for programming the ZX Spectrum”. It came from turning the project’s real constraints into a small body of permanent technical documentation and a reproducible build command.

From that point onward, Codex is no longer being asked to vaguely create a retro game in C.

It is working on this program, with this compiler, for this machine.

And that distinction changes almost everything.