The glorious futility of generating NAPLPS in 2023

Yeah! Actual real NAPLPS made by me!

NAPLPS — an almost-forgotten videotex vector graphics format with a regrettable pronunciation (/nap-lips/, no really) — was really hard to create. Back in the early days when it was a worthwhile Canadian initiative called Telidon (see Inter/Access’s exhibit Remember Tomorrow: A Telidon Story) it required a custom video workstation costing $$$$$$. It got cheaper by the time the 1990s rolled round, but it was never easy and so interest waned.

I don’t claim what I made is particularly interesting:

a lo-res red maple leaf in the bottom left corner of a black screen
suspiciously canadian

but even decoding the tutorial and standards material was hard. NAPLPS made heavy use of bitfields interleaved and packed into 7 and 8-bit characters. It was kind of a clever idea (lower resolution data could be packed into fewer bytes) but the implementation is quite unpleasant.

A few of the references/tools/resources I relied on:

Here’s the fragment of code I wrote to generate the NAPLPS:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# draw a disappointing maple leaf in NAPLPS - scruss, 2023-09

# stylized maple leaf polygon, quite similar to
# the coordinates used in the Canadian flag ...
maple = [
    [62, 2],
    [62, 35],
    [94, 31],
    [91, 41],
    [122, 66],
    [113, 70],
    [119, 90],
    [100, 86],
    [97, 96],
    [77, 74],
    [85, 114],
    [73, 108],
    [62, 130],
    [51, 108],
    [39, 114],
    [47, 74],
    [27, 96],
    [24, 86],
    [5, 90],
    [11, 70],
    [2, 66],
    [33, 41],
    [30, 31],
    [62, 35],
]


def colour(r, g, b):
    # r, g and b are limited to the range 0-3
    return chr(0o74) + chr(
        64
        + ((g & 2) << 4)
        + ((r & 2) << 3)
        + ((b & 2) << 2)
        + ((g & 1) << 2)
        + ((r & 1) << 1)
        + (b & 1)
    )


def coord(x, y):
    # if you stick with 256 x 192 integer coordinates this should be okay
    xsign = 0
    ysign = 0
    if x < 0:
        xsign = 1
        x = x * -1
        x = ((x ^ 255) + 1) & 255
    if y < 0:
        ysign = 1
        y = y * -1
        y = ((y ^ 255) + 1) & 255
    return (
        chr(
            64
            + (xsign << 5)
            + ((x & 0xC0) >> 3)
            + (ysign << 2)
            + ((y & 0xC0) >> 6)
        )
        + chr(64 + ((x & 0x38)) + ((y & 0x38) >> 3))
        + chr(64 + ((x & 7) << 3) + (y & 7))
    )


f = open("maple.nap", "w")
f.write(chr(0x18) + chr(0x1B))  # preamble

f.write(chr(0o16))  # SO: into graphics mode

f.write(colour(0, 0, 0))  # black
f.write(chr(0o40) + chr(0o120))  # clear screen to current colour

f.write(colour(3, 0, 0))  # red

# *** STALK ***
f.write(
    chr(0o44) + coord(maple[0][0], maple[0][1])
)  # point set absolute
f.write(
    chr(0o51)
    + coord(maple[1][0] - maple[0][0], maple[1][1] - maple[0][1])
)  # line relative

# *** LEAF ***
f.write(
    chr(0o67) + coord(maple[1][0], maple[1][1])
)  # set polygon filled
# append all the relative leaf vertices
for i in range(2, len(maple)):
    f.write(
        coord(
            maple[i][0] - maple[i - 1][0], maple[i][1] - maple[i - 1][1]
        )
    )

f.write(chr(0x0F) + chr(0x1A))  # postamble
f.close()

There are a couple of perhaps useful routines in there:

  1. colour(r, g, b) spits out the code for two bits per component RGB. Inputs are limited to the range 0–3 without error checking
  2. coord(x, y) converts integer coordinates to a NAPLPS output stream. Best limited to a 256 × 192 size. Will also work with positive/negative relative coordinates.

Here’s the generated file:

Applied Futility: Re-creating RAND’s ‘A Million Random Digits’

Sometimes, one must obey the inscrutable exhortations of one’s soul and travel deep into the inexplicable. The planet Why? has been left far behind, the chatter of its querulous denizens nothing more than a faint wisp of static. Where I’m going, pure patternlessness is all there is.

Page 53 from “A Million Random Digits …”, showing the five-digit sequential line number at left, followed by ten columns of five random digits each
A page from “A Million Random Digits …”, showing the five-digit sequential line number at left, followed by ten columns of five random digits each

In 1955, military-industrial complex stalwarts RAND Corporation published a huge book of just pages and pages of … digits. The digits were deliberately as random as possible, and were intended to help the fledgling practice of data science carry out truly random simulations. The book was called “A Million Random Digits with 100,000 Normal Deviates”. It was also made available on punched cards, with 50 digits to a card adding up to ten boxes of 2000 cards. A single box of punched cards was about 370 × 200 × 95 mm and weighed roughly 5 kg, so these digits had heft.

I thought it might be fun (or perhaps fun?: pronounced with a rising, questioning tone to stress the might-ness of any enjoyment arising) to dig into how RAND carried out this work, create some electronics to produce a similar random stream, and see how my random digits compare for randomness with RAND’s. For a final trick, I might even typeset the whole giant table into a book that no-one wants.

As I research and progress with this project, I’ll add in links here

I must stress: there is no reason for me to do this. A $5 micro-controller board can generate tens to hundreds of thousands of truly random digits per second. Any results I produce will have no use beyond my own amusement.


A Million Random Digits with 100,000 Normal Deviates is © Copyright 2001 RAND. Apart from a couple of page images and quotations from supporting material, none of that work is reproduced here. RAND does not support or endorse my futile efforts in any way.

importing Applesoft BASIC programs on the Apple IIe

Just what no-one has needed since about 1979 or so …

BASIC on the Apple II has no easy way to import text as a program. When you LOAD a file, it must be in Apple’s tokenized format. While Apple DOS has the EXEC facility to run script files as if they were typed from the keyboard, it’s very picky about the file format:

  1. There must be a carriage return character (CR, ASCII 13) before the first line
  2. All line numbers must have an extra space before and after them
  3. All tokens must be in upper case
  4. All tokens (keywords and functions) must have a space after them.

The right way to do this conversion would be to write a tokenizer that spits out the correct binary file. But you can (kinda) fudge it with this shell command, operating from BASIC source PROG.BAS:

sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/$/ /;s/[:()]/ & /g;' PROG.BAS | tr '\n' '\r' | ac.sh -p EG.dsk PROG T

ac.sh is the command line version of AppleCommander, and the file EG.dsk referred to above is an Apple DOS 3.3 image created with

ac.sh -dos140 EG.dsk

It still needs work, as there are functions that will mess this up, and Applesoft’s parser makes a mess of code like IF A THEN …, turning it into IF AT HEN ….

So if I wanted to import the following futile program:

10 REM A FUTILE PROGRAM BY SCRUSS
20 HOME
30 FOR X=1 TO 20
40 PRINT SPC(X);"FUTILE"
50 NEXT X

Run through the script (but before EOL conversion) it would look like this:

 10  REM A FUTILE PROGRAM BY SCRUSS 
 20  HOME 
 30  FOR X=1 TO 20 
 40  PRINT SPC ( X ) ;"FUTILE" 
 50  NEXT X

Make a disk and put the modified program text on it:

ac.sh -dos140 futile.dsk
sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/$/ /;s/[:()]/ & /g;' futile.bas | tr '\n' '\r' | ac.sh -p futile.dsk FUT T

Load the disk into your Apple II, clear out the init program, and import the code with EXEC FUT:

If all you get is ] cursors printed and no syntax errors, then something might be working. List it:

Run it:

Disk image: futile-AppleII-dsk.zip, containing:

$ ac.sh -l futile.dsk

DISK VOLUME #254
 T 002 FUT 
 A 002 FUTILE 
DOS 3.3 format; 134,144 bytes free; 9,216 bytes used.