Voidrunner: a single-player space trading door game for BBSes (NetBBS, Synchronet, Mystic, ENiGMA½, …)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗A persistent, single-player space trading and exploration door game for BBSes, in the tradition of Elite and Trade Wars. Trade across a 48-system galaxy, take contracts, fight or talk your way past raiders, hire a crew, join factions, and retire a career into the Hall of Fame.
Voidrunner is one Python file (voidrunner.py) with no dependencies beyond
the standard library. It needs Python 3.11 or newer and a terminal of at least
40×12. Saves are per-caller JSON files that the door manages itself.
python3 voidrunner.py in a terminal. It reads keys one
at a time and uses the window's size, as a BBS would, and leaves the terminal as it found it.
Set VOIDRUNNER_SAVE_DIR to keep a test career apart from your others.Careers live in the directory named by VOIDRUNNER_SAVE_DIR. NetBBS always
sets it, to <db path>.doors/voidrunner/ unless the SysOp chose another
place. Outside NetBBS, the default is ~/.voidrunner/. If that directory does
not exist and ~/.netbbs/voidrunner_saves/ does, the legacy directory is used
in place, without moving files. NetBBS's fallback remains the legacy directory,
including backup imports without a door environment when both directories exist.
Each pilot is one JSON file, keyed by the caller's numeric user id. The directory also holds
the session locks and the Hall of Fame records, so back it up as a whole and
don't delete lock files while a BBS is running.
Install Python 3.11+ and copy voidrunner.py to a persistent door directory.
Pass the caller's drop-file filename, quoted if it contains spaces:
python3 /opt/doors/voidrunner/voidrunner.py /path/to/node/DOOR.SYS
DOOR32.SYS also works. Filenames are matched without case sensitivity;
renamed files are recognized by their contents. Both CRLF and LF line endings
are accepted. The caller's numeric record number owns the save, so changing
an alias does not start a new career. NETBBS_DOOR_INFO, when set, takes
precedence over the command-line file, even if the JSON file cannot be read.
Malformed classic files emit a warning to stderr and use Guest defaults.
Set VOIDRUNNER_SAVE_DIR to an absolute, writable directory shared by every
node of one BBS, outside per-session temporary directories. Different BBSes
need separate save directories because their user numbers can overlap. A Unix
wrapper at /opt/doors/voidrunner/launch.sh can set it for every launch:
#!/bin/sh
export VOIDRUNNER_SAVE_DIR=/var/lib/voidrunner
exec /usr/bin/python3 /opt/doors/voidrunner/voidrunner.py "$1"
Make the wrapper executable and create the save directory with ownership for the BBS account. Configure the host as follows (adjust the example paths):
| Host | Stdio launch configuration |
|---|---|
| Synchronet on Linux | In SCFG's Online Programs, select Native Executable, Standard I/O, DOOR.SYS, and drop files in the Node Directory. Command line: /opt/doors/voidrunner/launch.sh "%f". |
| Mystic on Linux/macOS | Use the DD external-program menu command with /opt/doors/voidrunner/launch.sh "%PDOOR.SYS". %P includes the trailing slash. |
| ENiGMA½ | Use the abracadabra menu module with the configuration below. |
config: {
name: Voidrunner
dropFileType: DOOR
cmd: /usr/bin/python3
args: [ "/opt/doors/voidrunner/voidrunner.py", "{dropFilePath}" ]
io: stdio
encoding: cp437
env: { VOIDRUNNER_SAVE_DIR: "/var/lib/voidrunner" }
}
These are setup recipes awaiting the real-host checks in #6. Classic drop files select the
16-color basic preset for new ANSI careers, or plain for ASCII callers;
existing careers keep their saved Display Options choice. Encoding defaults to
CP437 for classic drop files and UTF-8 for standalone launches. In Display
Options, [E] cycles Automatic, UTF-8 and CP437 independently of the color
preset. Match the host's bridge encoding to that choice (the ENiGMA½ example
uses CP437). NetBBS always receives UTF-8 and handles its caller's encoding
itself, so its Display Options show a managed encoding. Saved names remain
Unicode; characters outside CP437 are transliterated for display only.
For a native DOOR32 socket launch on Windows or Unix, pass the same command-line
filename and configure the host to inherit the caller's connected socket.
DOOR32 line 1 must be 2 and line 2 must contain that inherited handle. The door
then sends and receives terminal traffic through the socket; it does not use
redirected stdin/stdout. A stale or invalid socket handle stops the launch with
a diagnostic on stderr. Comm type 0 and DOOR.SYS launches use stdio as before.
The socket transport filters telnet commands, escapes literal IAC output bytes,
and accepts CR-NUL or CR-LF as a single Enter. The host retains responsibility
for option negotiation. A disconnected caller follows the normal EOF checkpoint
path. Returning from the door closes its inherited handle without shutting down
the host's connection. Real Windows Synchronet validation remains in #6.
Known host time limits appear in each page's bottom border. The door warns at
three minutes remaining and saves before returning to the BBS, reserving up to
one second before the host cutoff. This also works while a prompt is unanswered.
The allowance comes from classic drop-file minutes or NetBBS's
session_limit_seconds, measured with a monotonic clock from door launch.
Absent or invalid limits leave sessions unbounded; an explicit zero expires
immediately. The limit belongs to this launch and is never stored in a career.
Pilots earn achievements across all of their careers (Pilot Record,
[A] Achievements, which an 80-column bar shortens to [A] Awards). Each one shows how many of the board's pilots hold it,
read from the same scores/ records the Hall of Fame uses, so back that
directory up with the saves.
On NetBBS v7.31.0 or later, a SysOp who switches game events on for this door
lets the node's chat bot announce promotions and silver or gold achievements
("Nib reached Merchant Captain in Voidrunner.") and answer !top voidrunner
from the Hall of Fame. Only what a caller earns while playing is reported: a
career that unlocks old achievements on its first launch in a new version sends
none of them. Other hosts, and NetBBS with events off, are unaffected.
In the animated presets (auto and basic) the door opens on a short title
splash and plays brief full-screen scenes during play: the jump, a contact, a
kill or the loss of the ship, an escape, a customs sweep, docking, salvage, a
rescue, a data burst, a tow, a hull refit, promotions, joining a faction and
retirement. Any key skips one, and each holds its last frame for two seconds.
The Viewport is drawn the same way: the pilot's ship, the station and any
landmark found, idling until a key. Callers who want none choose fast in
Display Options.
On a truecolor or 256-color terminal in the auto and fast presets, every
screen is drawn as the ship's console in front of a picture of where the ship
is: the system's world and station, the contact in a fight, the star map as a
chart of glowing systems and lanes. A page costs about 15 KB at 80x24 (30 KB at
120x36) the first time a place is shown, and about 5 KB per page turn after
that, against about 2 KB for a plain page. The
pictures use quadrant blocks, or half blocks for CP437 callers and for NetBBS
callers whose font lacks quadrants. A SysOp can switch the pictures off with
DOOR_CANOPY=0, or keep every caller to half blocks with DOOR_PIXELS=half. A SysOp can switch them off for everyone with DOOR_SPLASH=0
(the title splash) and DOOR_SCENES=0 (the in-flight scenes) in the door's
environment. A scene sends under 90 KB at 80x24 in truecolor, less at lower
color depths.
Host references: Synchronet DOOR.SYS, Mystic external programs, ENiGMA½ scripts and native binaries.
python -m pip install pytest pytest-xdist
python -m pytest -n auto # the suite, about half a minute
python tools/check_stdlib_only.py # the door imports only the standard library
python tools/door_gallery.py --open # every screen at 80/64/40 columns, every preset
python tools/door_gallery.py --encoding cp437 --out build/gallery-cp437
A change to a screen comes with gallery pictures: the tests can prove that a screen fits, never that it looks right. The game's design (rules, saves, presentation) is in docs/design.md.
Voidrunner was developed inside NetBBS until October 2026. Its history came along, but issue numbers in older commits, comments and the design text refer to Thiesi/NetBBS issues.
Pushing a tag such as v1.0.0 runs the suite and publishes that commit's
voidrunner.py, byte-for-byte as stored in git, with a SHA256SUMS file.
NetBBS vendors these releases.
Each release also carries netbbs.json, the NetBBS door profile from
profiles/, which SHA256SUMS covers too. A SysOp who runs a release before
NetBBS bundles it imports it with [J] Import JSON and corrects its paths.
It sets no VOIDRUNNER_SAVE_DIR: NetBBS passes the node's own save directory,
which its backups cover.
Every release's notes say whether that is safe, in one line:
NetBBS: saved data unchanged; runs on NetBBS 7.31.0 or later.
or, when the save layout or the maintenance_session / PilotBusy names that
NetBBS's backups import change:
NetBBS: changes the save format; wait for the NetBBS release that bundles it.
The banner is drawn from the game's own launch splash by art/render_banner.py.
BSD 2-Clause, see LICENSE.