darkzil is a small ZIL/Z-machine project template for creating Z3 text adventure games, with special care for running on Atari 8-bit computers.
It is intended for compact, old-school interactive fiction: dungeon crawls, sword-and-sorcery adventures, parser experiments, and Infocom-style games that should remain compatible with constrained 8-bit targets.
The goal of this project is to provide a simple, understandable base for writing Z-machine Version 3 games in ZIL.
The code is kept deliberately small and conservative so that games can run not only in modern interpreters such as Frotz, but also on classic Atari 8-bit computers.
-
Z-machine Version 3 / Z3 target
-
Atari 8-bit friendly design
-
Small custom parser
-
Compact standard-library style support file
-
Room, object, inventory, and movement handling
-
Basic verbs such as:
LOOKEXAMINESEARCHTAKE/GETDROPOPENCLOSEUSETALKGIVEKILLSAVERESTORERESTARTQUIT
-
Confirmation prompts for dangerous actions
-
Support for old-style adventure mechanics:
- locked doors
- light sources
- traps
- puzzle objects
- room descriptions
- inventory state
-
Conversation System
-
Novel RPG elements based on DarkRPG system:
- attributes
- combat
- automatic or manual dice rolls
darkzil/
├── darkpit.zil # Main game source
├── minilib.zil # Minimal support library / parser / common routines
├── a8.bin.green # Atari 8-bit Z-machine interpreter, green-screen variant
├── a8.bin.amber # Atari 8-bit Z-machine interpreter, amber-screen variant
├── zil # Build/run helper script
└── README.md
Copy zil and a8.bin files to your /usr/local/bin. Make zil executable.
This project is intended to be built with ZILF. It also includes its own wrapper tool, zil, which handles most build and workflow tasks.
Example:
zil darkpit.zilThis should produce a Z-machine story file, usually with a .z3 extension
plus Atari double density (dd) ATR disk image.
You can download ZILF from https://zilf.io
The main target is:
Z-machine Version 3
Atari 8-bit
The code avoids relying on modern interpreter conveniences where possible.
Because Atari 8-bit interpreters are much stricter and more memory-constrained than modern ones, the project tries to follow old Infocom-style limitations.
DarkRPG is a general-purpose tabletop role-playing game system created by Andy Kosela. It is designed as a simple, flexible rules framework for running old-school fantasy, sword-and-sorcery, dungeon-crawling, or science-fiction adventure campaigns. Rather than being tied to a single setting, DarkRPG provides a compact foundation for characters, conflict, exploration, and game-master adjudication, allowing it to be adapted to different worlds, tones, and styles of play.
The system draws on many years of experience with some of the most influential role-playing games in the history of the hobby, including Basic D&D, AD&D, Traveller, GURPS, Warhammer Fantasy Roleplay, RuneQuest, Cyberpunk 2020, Fighting Fantasy, QuestGame, and MERP. It aims to preserve the clarity, danger, imagination, and freedom of classic RPG design while keeping the rules compact and practical for actual play.
Both DarkZIL and DarkRPG projects are interconnected with Andy Kosela's DarkSim, a 3D simulation engine based on Looking Glass Studios' Dark Engine powering Thief: The Dark Project and System Shock 2 games.
The game uses a small RPG attribute system called DarkRPG.
To display your current attributes, type:
A
The game prints:
S:<value>, D:<value>, K:<value>, W:<value>, HP:<value>, AP:<value>
The attributes are:
S Strength
D Dexterity
K Knowledge
W Weapon skill
HP Health points
AP Armor points
Strength shows how physically strong the player is.
It affects starting health:
HP = 6 + S
A weak character has fewer hit points.
A strong character has more hit points.
Dexterity shows how quick and agile the player is.
It is displayed as part of the character sheet and may be used by the game for agility-style checks.
Knowledge shows what study, learning, and practical understanding the player has.
It is displayed as part of the character sheet and may be used by the game for knowledge-based checks.
Weapon skill shows how well the player fights with weapons.
It is added to the player's combat roll:
player attack = 1d6 + W
Higher Weapon skill makes the player more likely to win in combat.
Health points show how much injury the player can survive.
If HP falls to zero, the player dies.
When killed, the game prints a game-over message and asks whether to restart.
Armor points reduce incoming damage.
Armor items increase AP.
Examples:
dented iron helmet AP +1
shirt of dark chainmail AP +2
Incoming damage is reduced like this:
final damage = raw damage - AP
Damage cannot go below zero.
If armor reduces damage to zero, the blow is stopped.
To see the in-game RPG help, type:
RPG
Starting attributes are rolled at the beginning of the game.
Each attribute is rolled with 2d6.
The 2d6 result is converted into an attribute modifier:
2-4 = -1
5-8 = 0
9-10 = +1
11-12 = +2
The game rolls:
Strength/S
Dexterity/D
Knowledge/K
Weapon/W
Then HP is calculated:
HP = 6 + S
Example:
S:1, D:0, K:1, W:2, HP:7, AP:0
This means:
S +1 strong
D 0 average
K +1 learned
W +2 expert
HP 7
AP 0
Combat is simple and round-based.
Use:
KILL <npc>
or the short form:
K <npc>
Examples:
KILL GUARD
K GUARD
If there is only one obvious enemy in the room, you can usually type:
KILL
or:
K
and the game will choose that target automatically.
You cannot fight properly while chained. Free yourself first.
When fighting the guard, one combat round happens each time you attack.
The combat round compares:
player roll = 1d6 + W
guard roll = 1d6 + guard W
The game prints both results.
Example:
You roll 5; the guard rolls 3.
If the player rolls higher, the player hits the guard.
If the guard rolls higher, the guard hits the player.
If both rolls are equal, neither side gains the advantage.
If the player has a sword, player damage is:
1d6
The old sword and the guard's sword both do normal sword damage.
If the player fights without a sword, damage is much weaker.
Bare-handed damage is reduced from the 1d6 roll:
1 = 0 damage
2-3 = 1 damage
4-5 = 2 damage
6 = 3 damage
The guard has armor, so bare-handed attacks are usually useless against him.
In practice, find a sword before trying to kill the guard.
The guard has armor:
guard AP = 3
After the player rolls damage, the guard's AP reduces it.
Example:
raw damage = 5
guard AP = 3
final damage = 2
If final damage is zero, the game prints that the guard's helm and chainmail turn the blow.
If the guard wins the combat roll, he rolls:
1d6 damage
Your AP reduces this damage.
Example:
raw damage = 4
your AP = 2
final damage = 2
If your AP reduces damage to zero, your armor turns the blow.
If the guard is present and threatening, wasting time is dangerous.
Commands that are not movement and not an attack against the guard may allow him to strike you.
The guard's punishment strike does:
2 damage
So if the guard is in the room, do not waste turns unless you are ready to take a hit.
The game supports both automatic and manual dice rolls.
By default, dice rolls are automatic.
In automatic mode, the game rolls internally.
To toggle dice mode, type:
DICE
When manual dice mode is turned on, the game prints:
Manual dice rolls are now on. Roll real dice and type the result when asked.
For manual starting attributes, type RESTART now.
When manual dice mode is turned off, the game prints:
Automatic dice rolls are now on.
For a 1d6 roll, roll one real six-sided die and type the result.
Example prompt:
Roll 1d6 for your attack:
Valid input:
1
2
3
4
5
6
For a 2d6 roll, roll two real six-sided dice and type the total.
Example prompt:
Roll 2d6 for Strength/S (2-12):
Valid input:
2
3
4
5
6
7
8
9
10
11
12
If the number is outside the valid range, the game asks again.
Starting attributes are rolled when the game starts.
If you want to roll starting attributes manually, do this:
DICE
RESTART
After restart, the game asks you to manually enter the 2d6 totals for:
Strength/S
Dexterity/D
Knowledge/K
Weapon/W
Combat uses dice rolls for attack and damage.
Player attack:
1d6 + W
Guard attack:
1d6 + guard W
Player sword damage:
1d6
Guard damage:
1d6
In automatic mode, the game rolls these internally.
In manual mode, you roll real dice and type the result.
The game includes a menu-based conversation system for NPCs.
To start a conversation, type:
TALK TO <npc>
When a conversation begins, the game displays a list of dialogue choices marked with letters:
The old man lifts his head. His beard is white with dust and his eyes gleam in the gloom.
Ask the old man about:
A. Himself
B. The guard
C. The deep places
D. End conversation
The final option always ends the conversation and returns to normal command mode.
While in conversation mode, normal movement and object commands are not used. Choose one of the displayed letter options to continue or end the conversation.
The game uses a simple carrying limit.
At the start, you can carry only 2 items, representing what you can hold in your hands.
An old backpack can be found in the Old Armory.
Once you take the backpack, your carrying limit increases to 10 items total:
2 items in your hands
8 more items in the backpack
The inventory command does not print item counts. It only lists what you are carrying.
Use:
INVENTORY
or:
I
To take everything visible in the current room:
TAKE ALL
GET ALL
G ALL
To drop everything you are carrying:
DROP ALL
DROP ALL drops the backpack last, so it works even when you are carrying more than two items.
If you are carrying more than two items, you cannot drop the backpack by itself. You must drop enough items first.
The game includes a small hidden engine console. It behaves like a minimal system monitor/debugger rather than an in-world magic system.
Enter it from normal gameplay with either:
CONSOLE
or:
CON
The game then asks for a password:
PASSWORD:
The password is:
zork
Successful login prints:
220 SYS MONITOR READY. Q=RTS.
The normal game prompt > changes to the console prompt *.
Exit the console with:
Q
or:
QUIT
The console exits with:
221 RTS.
Console commands do not advance normal game turns.
| Opcode | Arguments | Meaning |
|---|---|---|
HLP |
none | Print console help. |
WHE |
none | Print current room, turn count, HP/AP, attributes, lamp state, and guard state. |
FLG |
none | Dump important game flags. |
ROM |
none | Print the room symbol table: available room addresses for GOT. |
FIL |
none | Print the files (objects) symbol table. |
GOT |
room address | Teleport to a room. |
GET |
object file | Force an object into inventory. |
DRP |
object file | Drop an object into the current room. |
HEA |
none | Restore HP to derived maximum: 6 + S. |
HUR |
none | Subtract 1 HP. |
KIL |
none | Kill/disable the guard. |
OPN |
none | Force-open or patch the relevant local barrier. |
FRE |
none | Clear the starting chain flag. |
DRN |
none | Reveal the lever and mark the well as drained. |
LIT |
none | Put lamp in inventory and mark it lit. |
DUS |
none | Mark lamp dark/unlit. |
REV |
none | Reveal or mark local hidden content/trap state. |
Q |
none | Exit console, like RTS. |
QUIT |
none | Exit console, like RTS. |
Use these with GOT:
CEL HAL ARM COR GRD ALC NIC CRT VLT STA SPR BRG BON WEL SHR
| Address | Room |
|---|---|
CEL |
Starting cell |
HAL |
Dungeon hall |
ARM |
Old armory |
COR |
East corridor |
GRD |
Guard room |
ALC |
Dark alcove |
NIC |
Hidden niche |
CRT |
Ruined court |
VLT |
Vault |
STA |
Stair |
SPR |
Spear room |
BRG |
Bridge |
BON |
Bone room |
WEL |
Black well |
SHR |
Forgotten shrine |
Example:
*GOT WEL
250 CWD OK.
Use these with GET and DRP:
BON BPK CHS FLA GEM GSW HEL KEY LMP MAL SWD
| File | Object |
|---|---|
BON |
Bone |
BPK |
Backpack |
CHS |
Chisel |
FLA |
Flask of water |
GEM |
Gem |
GSW |
Guard sword |
HEL |
Helmet |
KEY |
Key |
LMP |
Lamp |
MAL |
Chainmail |
SWD |
Sword |
Examples:
*GET SWD
226 RETR OK.
*DRP SWD
250 STOR OK.
>CON
PASSWORD:zork
220 SYS MONITOR READY. Q=RTS.
*HLP
OPCODES:
HLP dir WHE pwd/stat
FLG flag dump ROM symtab
GOT <addr> GET <file>
DRP <file> HEA restore hp
HUR damage KIL kill guard
OPN patch door FRE free chain
DRN drain LIT lamp on
DUS lamp off REV reveal
Q / QUIT FIL symtab
211 END
*WHE
257 CWD=Well of Black Waters
213 TURNS=0 HP=8 AP=0
213 S=2 D=1 K=0 W=1
213 LAMP=DARK
213 GUARD-HP=6
*GOT SHR
250 CWD OK.
*GET SWD
226 RETR OK.
*HEA
250 HP RESTORED.
*Q
221 RTS.
>
By default, the console password is:
zork
The password check is implemented in minilib.zil, in these routines:
<ROUTINE SYS-VEC? ...>
<ROUTINE SYS-VEC-S? ...>The password is not stored directly as the plain text word zork. Instead, each character is checked as a small numeric value.
The current password:
zork
is encoded as:
z = 122 - 100 = 22
o = 111 - 100 = 11
r = 114 - 100 = 14
k = 107 - 100 = 7
So the current password check in SYS-VEC? looks like this:
<COND
(<EQUAL? .N 1> <EQUAL? <- .L 100> 22>)
(<EQUAL? .N 2> <EQUAL? <- .L 100> 11>)
(<EQUAL? .N 3> <EQUAL? <- .L 100> 14>)
(<EQUAL? .N 4> <EQUAL? <- .L 100> 7>)
(T <RFALSE>)>>To change the password, choose a new lowercase password and convert every character like this:
character ASCII code - 100
For example, to change the password to:
doom
calculate:
d = 100 - 100 = 0
o = 111 - 100 = 11
o = 111 - 100 = 11
m = 109 - 100 = 9
Then replace the values in SYS-VEC? with:
<COND
(<EQUAL? .N 1> <EQUAL? <- .L 100> 0>)
(<EQUAL? .N 2> <EQUAL? <- .L 100> 11>)
(<EQUAL? .N 3> <EQUAL? <- .L 100> 11>)
(<EQUAL? .N 4> <EQUAL? <- .L 100> 9>)
(T <RFALSE>)>>If the new password has a different length, also update the length check in SYS-VEC-S?.
For the current four-letter password, the check is:
(<NOT <EQUAL? .LEN 4>>
<RFALSE>)For a five-letter password, change it to:
(<NOT <EQUAL? .LEN 5>>
<RFALSE>)Then add another character check in the same routine.
For example, a five-letter password would need five checks:
<AND
<SYS-VEC? <GETB ,INBUF .POS> 1>
<SYS-VEC? <GETB ,INBUF <+ .POS 1>> 2>
<SYS-VEC? <GETB ,INBUF <+ .POS 2>> 3>
<SYS-VEC? <GETB ,INBUF <+ .POS 3>> 4>
<SYS-VEC? <GETB ,INBUF <+ .POS 4>> 5>>Then also add the fifth value in SYS-VEC?:
(<EQUAL? .N 5> <EQUAL? <- .L 100> VALUE>)Replace VALUE with the calculated value for the fifth character.
For best compatibility with old Z-machine V3 interpreters, keep the password
short and use only simple lowercase letters a to z.
When writing new content, keep these constraints in mind:
Z3 vocabulary words are limited. In practice, keep parser words short and avoid long synonyms.
Good:
(SYNONYM DOOR GATE)Risky:
(SYNONYM MANACLES SHACKLES)Long words may fail compilation or behave badly on older interpreters.
Z3 games have stricter memory and object limits than later Z-machine versions. Keep object count reasonable.
For portability, avoid manually breaking every line for a 40-column screen. Instead, write paragraph-style text and let each interpreter wrap naturally.
Good:
<TELL "The corridor bends beneath the earth, its stones black with damp and age." CR CR>Avoid hard-wrapping prose unless absolutely necessary.
This project uses a small manual parser rather than a full Infocom library. When adding a new command, make sure it is wired into the actual dispatch logic, not only added as a vocabulary synonym.
The release number can be set in the source with:
<CONSTANT RELEASEID 0>The serial number is normally generated from the build date in YYMMDD format.
Example:
Release 0 / Serial number 260506
To print the release and serial number at startup, the game must explicitly print them in its banner routine.
The included darkpit.zil is a small sword-and-sorcery dungeon adventure demo.
It includes:
- an opening prison cell
- chains puzzle
- guard encounter
- lamp/light mechanics
- dungeon rooms
- traps
- final black gem puzzle
It is meant both as a playable demo and as a reference for building your own Z3 adventure.
darkzil favors:
- small code
- predictable behavior
- old interpreter compatibility
- readable ZIL
- simple adventure mechanics
- manual control over parser behavior
It does not try to be a complete Inform-like library. It is closer to a compact framework for learning and experimenting with ZIL on real or emulated 8-bit machines.
When adding new rooms:
- Define the room object.
- Add exits.
- Add room description text.
- Add any room-specific search or trap logic.
- Test movement in both Frotz and an Atari emulator.
When adding new objects:
- Add a constant/object ID if needed.
- Add vocabulary.
- Add placement/state globals if the object moves.
- Add it to room/object printing.
- Add take/drop/inventory behavior if portable.
- Add special verb handling if it is part of a puzzle.
When adding new verbs:
- Add vocabulary words.
- Add parser dispatch handling.
- Add the action routine.
- Add help text.
- Test abbreviations manually.
A game built with darkzil should ideally work in:
- Frotz
- Bocfel
- Parchment (play it online)
- other modern Z-machine interpreters
- Atari 8-bit computers
The Atari target is the strictest one, so if something works there, it will usually work elsewhere.
MIT License
Experimental but usable.
darkzil is intended as a practical base for Z3 adventure games, especially those meant to feel at home on classic Atari 8-bit systems.