Skip to content

Instantly share code, notes, and snippets.

@rsbohn
Created January 3, 2026 18:38
Show Gist options
  • Select an option

  • Save rsbohn/e85e11e96def1975de4ace95b71431ae to your computer and use it in GitHub Desktop.

Select an option

Save rsbohn/e85e11e96def1975de4ace95b71431ae to your computer and use it in GitHub Desktop.
Nova 1210 Game Location Tape - TC08 Demo with 64 Adventure Locations
#!/usr/bin/env python3
"""Create a TU56 DECtape image with place names for a game."""
import struct
import sys
# TC08 constants
WORDS_PER_BLOCK = 129
DATA_WORDS_PER_BLOCK = 128
BLOCKS = 0o100 # 64 decimal blocks
# Place names (64 interesting locations)
PLACES = [
"The Ancient Harbor",
"Misty Moorlands",
"Crystal Caverns",
"Abandoned Observatory",
"Whispering Woods",
"Forgotten Temple",
"Floating Islands",
"Underground Lake",
"Storm Peak Summit",
"Desert Oasis",
"Frozen Tundra",
"Volcanic Forge",
"Sunken City Ruins",
"Enchanted Garden",
"Shadow Valley",
"Celestial Tower",
"Deep Forest Glade",
"Rocky Cliffs",
"Merchant's Bazaar",
"Hidden Sanctuary",
"The Great Library",
"Moonlit Beach",
"Mountain Pass",
"Old Mill",
"Dragon's Lair",
"Sacred Grove",
"Windswept Plains",
"Coral Reef",
"Ice Palace",
"Ruins of Atlantis",
"Mystic Falls",
"Bone Yard",
"Golden Fields",
"Dark Abyss",
"Sky Bridge",
"Emerald Mines",
"Ghost Town",
"Serpent's Nest",
"Lighthouse Point",
"Canyon Echo",
"Wizard's Tower",
"Fishing Village",
"Jungle Canopy",
"Stone Circle",
"Pirate Cove",
"Silver Lake",
"Thunder Mountain",
"Silk Road Outpost",
"Swamp of Sorrows",
"Paradise Valley",
"Fortress Ruins",
"Starlight Plateau",
"Burning Sands",
"River Crossing",
"Cloud City",
"Haunted Mansion",
"Pearl Lagoon",
"Amber Forest",
"The Crossroads",
"Sapphire Grotto",
"Eternal Spring",
"Ravens Nest",
"The Lost Garden",
"Iron Gate Keep",
]
def string_to_words(text):
"""Convert ASCII string to 16-bit words (Nova format: high byte first, low byte second)."""
# Pad to even length
if len(text) % 2 == 1:
text += '\0'
words = []
for i in range(0, len(text), 2):
# First char goes in high byte, second char in low byte
word = (ord(text[i]) << 8) | ord(text[i + 1])
words.append(word)
# Add terminating zero if not already present
if not words or words[-1] != 0:
words.append(0)
return words
def create_tape_image(filename):
"""Create a TU56 tape image with place names."""
with open(filename, 'wb') as f:
for block_num in range(BLOCKS):
# Get place name for this block
place_name = PLACES[block_num] if block_num < len(PLACES) else f"Location {block_num}"
# Convert to words
name_words = string_to_words(place_name)
# Ensure we don't exceed 64 words
if len(name_words) > 64:
name_words = name_words[:64]
# Pad to 128 data words
block_data = name_words + [0] * (DATA_WORDS_PER_BLOCK - len(name_words))
# Write 128 data words (BinaryReader reads little-endian)
for word in block_data[:DATA_WORDS_PER_BLOCK]:
f.write(struct.pack('<H', word)) # Little-endian uint16
# Write the 129th word (block number/checksum - set to 0)
f.write(struct.pack('<H', 0))
print(f"Created {filename} with {BLOCKS} blocks ({BLOCKS * WORDS_PER_BLOCK * 2} bytes)")
print(f"First 10 locations:")
for i in range(min(10, len(PLACES))):
print(f" Block {i:03o} (dec {i:2d}): {PLACES[i]}")
if __name__ == '__main__':
output_file = sys.argv[1] if len(sys.argv) > 1 else 'game.tu56'
create_tape_image(output_file)
; Game location demo - Display place names from TU56 tape
;
; This program demonstrates reading location names from the game tape.
; Each of the 64 blocks contains a place name in the first 64 words.
;
; Usage:
; 1. Attach tape: tc0 attach game.tu56
; 2. Assemble: asm sd/gametest.asm 100
; 3. Run: go 100
;
; The program uses the RTC to select a random block (0-63), loads it
; from TC08 device 0o20 (TC0), and prints the zero-terminated ASCII
; string (2 chars per word, high byte first).
ORG 0o100
START:
; Get random block using RTC (device 0o21)
; DIB returns epoch seconds (low word) - changes every second
DIB 0, 0o21 ; Read epoch seconds (low) into AC0
LDA 1, MASK77
AND 1, 0 ; Mask to 0-63 range
STA 0, RNDBLK ; Save block number
; Read the block from tape using TC08 device (0o20)
; TC08 protocol:
; DOA = set transfer address
; DOB = set block number
; DOC = set control (bit 0=drive, bit 1=read, bit 2=write)
; NIOS = start transfer
; SKPDN = skip if done
LDA 0, BUFBASE ; Transfer address
DOA 0, 0o20 ; Set address in TC08
LDA 0, RNDBLK ; Block number
DOB 0, 0o20 ; Set block in TC08
LDA 0, RDCMD ; Control: drive 0, read
DOC 0, 0o20 ; Set control in TC08
NIOS 0, 0o20 ; Start I/O
WAIT: SKPDN 0o20 ; Skip if done
JMP WAIT ; Wait for completion
; Check for error (bit 2 of status)
DIC 0, 0o20 ; Read status
LDA 1, ERRMASK ; Error bit mask
AND 1, 0 ; Test error bit
MOV 0, 0, SZR ; Skip if zero (no error)
JMP ERROR ; Jump to error handler
; Print the location name from buffer (memory 0o1000)
LDA 2, BUFBASE
JSR PRTSTR
; Print newline
LDA 0, NEWLINE
DOA 0, TTO
; Done - halt
HALT
ERROR:
; Print error message
LDA 2, ERRMSG
JSR PRTSTR
HALT
; Print string pointed to by AC2 (zero-terminated, 2 chars per word)
; Destroys AC0, AC1
PRTSTR:
STA 3, RETADR ; Save return address
PS1: LDA 0, 0,2 ; Get word from string
MOV 0, 0, SZR ; Test for zero, skip if zero
JMP PSWORD ; Not zero, print it
JMP @RETADR ; Zero, return
PSWORD: STA 0, TMPWORD ; Save word
MOVZR 0, 0 ; Shift right 8 bits
MOVZR 0, 0
MOVZR 0, 0
MOVZR 0, 0
MOVZR 0, 0
MOVZR 0, 0
MOVZR 0, 0
MOVZR 0, 0
MOV 0, 0, SZR ; Test if zero, skip if zero
DOA 0, TTO ; Output high byte (skipped if zero)
LDA 0, TMPWORD ; Get word again
LDA 1, LOMASK
AND 1, 0 ; AC0 = LOMASK & word (get low byte)
MOV 0, 0, SZR ; Test if zero, skip if zero
DOA 0, TTO ; Output low byte (skipped if zero)
INC 2, 2 ; Increment pointer (AC2 = AC2 + 1)
JMP PS1 ; Next word
; Data
RNDBLK: DW 0
BUFBASE: DW 0o1000
MASK77: DW 0o77
LOMASK: DW 0o377
NEWLINE: DW 0o012
RDCMD: DW 0o2 ; TC08 control: read (bit 1)
ERRMASK: DW 0o4 ; TC08 status: error (bit 2)
RETADR: DW 0
TMPWORD: DW 0
; Error message "ERROR\n"
ERRMSG: DW 0o105122, 0o122117, 0o122012, 0

Nova 1210 Game Location Tape

A demonstration of TC08 DECtape I/O and RTC-based random block selection on the Data General Nova 1210 emulator.

What's This?

This is a simple game demo that:

  • Creates a TU56 DECtape image with 64 location names
  • Uses the RTC (real-time clock) for per-second randomness
  • Loads random blocks from TC08 device using proper I/O protocol
  • Displays location names from an adventure game world

Files

  • gametest.asm - Nova assembly program demonstrating TC08 block loading
  • create_game_tape.py - Python script to create the game.tu56 tape image

Quick Start

# 1. Create the tape image
python3 create_game_tape.py game.tu56

# 2. Run the emulator (requires https://github.com/rsbohn/dusky-petrel)
dotnet run --project snova/Snova.csproj

# 3. In the emulator:
snova> tc0 attach game.tu56
snova> asm gametest.asm 100
snova> go 100
Mystic Falls

# Run again for a different location (uses RTC seconds)
snova> go 100
Dragon's Lair

How It Works

  1. RTC DIB (device 0o21) provides epoch seconds (low word) - changes every second
  2. Value is masked to 0-63 to select one of 64 blocks
  3. TC08 protocol (device 0o20) loads the block:
    • DOA: Set transfer address
    • DOB: Set block number
    • DOC: Set control (0o2 = read)
    • NIOS: Start transfer
    • SKPDN: Wait for completion
  4. Displays the location name from buffer

The 64 Locations

The tape contains adventure game locations like:

  • The Ancient Harbor
  • Crystal Caverns
  • Dragon's Lair
  • Floating Islands
  • Hidden Sanctuary
  • Wizard's Tower
  • ... and 58 more!

Each block has space for 64 words (128 bytes) for the location name, with remaining space available for future descriptions.

Technical Details

  • Tape Format: TU56 DECtape, 64 blocks, 129 words per block
  • Encoding: Little-endian 16-bit words (BinaryReader format)
  • Strings: 2 ASCII chars per word, high byte first, zero-terminated
  • Device Codes: TC08=0o20, RTC=0o21, TTY=0o11

Requirements

  • Dusky Petrel - Nova 1210 emulator
  • Python 3 (for creating the tape image)
  • .NET 9.0+ (for running the emulator)

License

Public domain / CC0 - Use however you like!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment