EN /HU | Login

Commodore 64

A Commodore 64 can call a rubbs board. session.petscii! switches the line to PETSCII (40×25, the C64's colours and keys) while the code above keeps writing UTF-8 with ANSI; BBS::Terminal asks the caller which machine is calling; and a two-faced board serves a modern face and a C64 face on the same port. This page covers all three, with Feedia and Alexandria BBS as worked examples, and how a real or emulated C64 gets on the line. Available since 0.15.0 (BBS::Petscii) and 0.16.0 (BBS::Terminal).

How a C64 calls

A Commodore 64 has no network port. It reaches a telnet board through a terminal program and a modem:

Part What callers use
Terminal program CCGMS (CCGMS Future), StrikeTerm, or SyncTERM in C64 mode on a PC. It must be in PETSCII mode (no ASCII translation), in the lowercase character set.
Modem A WiFi modem (Zimodem, WiModem232, and the like) on the user port or behind a SwiftLink/Turbo232 cartridge, typically 38400 baud with RTS/CTS on a SwiftLink. It is dialled with ATDT host:port, e.g. ATDT teletypegames.org:2323.

What the board sees:

  • Plain TCP, no telnet. A WiFi modem usually opens a raw TCP connection. The C64 never answers the board's telnet negotiation and never reports a window size (NAWS). The negotiation bytes reach the C64 as a few stray characters before the first screen; that is harmless.
  • PETSCII bytes in both directions. Letters are swapped against ASCII, colours and cursor moves are single control bytes, RETURN is a lone CR.
  • 40×25, and the C64 wraps by itself after column 40.

That is why rubbs guesses "C64" when no window size comes, and why the PETSCII line turns everything into single bytes rather than escape sequences.

Testing with VICE

The VICE emulator (x64sc) can play the C64, the SwiftLink and the modem line: VICE's RS-232 device connects straight to a TCP host (+rsdev1ip232 means raw TCP, no IP232 protocol), and CCGMS on a disk image is the terminal program.

SH
# A C64 with a SwiftLink at $DE00 (NMI), its line connected to a board over TCP,
# CCGMS autostarted from a disk image; CCGMS's saved config sets the SwiftLink to 38400.
x64sc -silent +cart \
  -acia1 -acia1base 0xDE00 -acia1irq 1 -acia1mode 1 -myaciadev 0 \
  -rsdev1 127.0.0.1:2323 -rsdev1baud 38400 +rsdev1ip232 \
  -autostart ccgms.d64

With -rsdev1 the connection is already open when CCGMS starts, so no ATDT is needed; the board asks its first question, and RETURN takes the guess (C64). To check bytes without an emulator, connect with a small script, send no NAWS, and look at what comes back (see the example under BBS::Terminal below).

The PETSCII line

session.petscii! and session.petscii?

Method What it does
session.petscii! From now on the session talks to a C64. It sets term_cols / term_rows to 40×25, switches mouse reporting off for good (enable_mouse becomes a no-op), sends LOWERCASE, LOCK_CASE and CLEAR raw (the lowercase character set, C= + Shift locked, a clear screen), and puts a BBS::Petscii::Encoder in front of write. Calling it again does nothing.
session.petscii? true once petscii! has run.

After petscii!:

  • every session.write goes through the Encoder, so a Flow, a FakeOS, an Application or a door keeps writing UTF-8 text with ANSI colours and cursor moves;
  • readkey and readline decode the C64's keys (see The C64's keys below);
  • a NAWS report from the client no longer changes the size: the line stays 40×25.

A Flow, a FakeOS or a door drawn for 40 columns runs on a C64 unchanged; one drawn for 80 columns wraps.

RUBY
require 'bbs'

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    call do |_ctx, runner|
      session = runner.session
      session.petscii!
      session.write "\e[1;33mREADY.\e[0m\r\n"
      session.write "#{session.term_cols}x#{session.term_rows}, petscii: #{session.petscii?}\r\n"
      nil
    end
  end
end

BBS.start

BBS::Petscii constants

Screen and control codes

The constants are the PETSCII codes as one-character UTF-8 strings (the C0/C1 code points), so they can be written straight into a string: "#{BBS::Petscii::YELLOW}READY." is yellow on the C64.

Constant Code Meaning
COLS, ROWS 40, 25 The C64's text screen.
CLEAR 0x93 Clear the screen, cursor home.
HOME 0x13 Cursor to the top left corner.
UP, DOWN, LEFT, RIGHT 0x91, 0x11, 0x9d, 0x1d Cursor moves.
RVS_ON, RVS_OFF 0x12, 0x92 Reverse video on and off. RETURN also ends reverse mode.
DEL 0x14 Delete (backspace) on the C64.
BELL 0x07 Bell.
LOWERCASE, UPPERCASE 0x0e, 0x8e Switch to the lowercase (text) or the uppercase (graphics) character set.
LOCK_CASE 0x08 Lock the character set, so C= + Shift no longer switches it. This is ASCII's backspace, and the Encoder turns 0x08 into a cursor-left: send it raw (petscii! does).
CONTROLS array The control bytes the Encoder passes through to the C64 as they are.
DEFAULT LIGHT_GREY The colour of plain text (\e[0m).

Colours

The C64 has 16 text colours. All of them can be written directly; most are also reachable from ANSI.

Constant Code ANSI that gives it
WHITE 0x05 \e[1;37m, \e[97m
RED 0x1c \e[31m
GREEN 0x1e \e[32m
BLUE 0x1f \e[34m
ORANGE 0x81 \e[33m (dark yellow)
BLACK 0x90 (none: write the constant)
BROWN 0x95 (none: write the constant)
LIGHT_RED 0x96 \e[1;31m, \e[91m
DARK_GREY 0x97 \e[30m, \e[1;30m, \e[90m (black is dark grey: a black-on-grey door must stay readable on the C64's black screen)
GREY 0x98 \e[2m, \e[2;37m (dim)
LIGHT_GREEN 0x99 \e[1;32m, \e[92m
LIGHT_BLUE 0x9a \e[1;34m, \e[94m
LIGHT_GREY 0x9b \e[37m, \e[0m (the default)
PURPLE 0x9c \e[35m, \e[1;35m
YELLOW 0x9e \e[1;33m, \e[93m
CYAN 0x9f \e[36m, \e[1;36m

COLORS lists all 16; ANSI_DARK and ANSI_BRIGHT are the two rows of the ANSI mapping (index 0–7 = black, red, green, yellow, blue, magenta, cyan, white). Bold (1) picks the bright row. Background colours (40–47) and 256/true colours (38;5;n, 38;2;r;g;b, 48;…) are dropped: the background of a C64 terminal is the terminal program's.

The Encoder: what happens to what you write

BBS::Petscii::Encoder#encode(data) turns a string into PETSCII bytes (a binary string). The session keeps one Encoder per call, because the Encoder has state: the cursor column, the current colour, reverse mode, and an escape sequence cut in two by separate writes.

Input Output on the C64
A–Z / a–z Swapped the PETSCII way: A → 0xc1, a → 0x41, so they show in the right case in the lowercase set.
Digits, space, !"#$%&'()*+,-./:;<=>?@[] As they are.
Box drawing ─│┌┐└┘├┤┬┴┼ (single, double, heavy, rounded) The C64's line glyphs. The lowercase set has no rounded corners, so ╭╮╰╯ show as the square ┌┐└┘; the uppercase (graphics) set has them (Petscii::ROUNDED_CORNERS, drawn by a Petscii::Canvas).
█ A reversed space. ▀ ▐ are reversed half blocks, ▄ ▌ ▁ ▔ the C64's own; ░▒▓ a checker glyph, ▖▗▘▝▚ the C64's quarter blocks.
£ ← ↑ ✓ The C64's own £, ←, ↑, check mark.
\ { } ~ ^ _ \| and ` | /, (, ), -, ↑, an underline glyph, a vertical line, '.
Typography – — ‘ ’ “ ” … · • ● ◄ ► → × ° The nearest ASCII: -, ', ", ..., ., *, <, >, x, o.
Accented letters á é ő ű Ä The letter without its accent (Unicode NFKD, marks removed): á → a, Ő → O. ß → ss, π → pi.
Anything else (€, emoji, CJK) ?.
\r\n One RETURN (0x0d).
\n alone One RETURN.
\r alone Back to the start of the same line (RETURN + cursor up).
\t Two spaces.
\b (0x08) Cursor left.
\f (0x0c), \e[2J CLR.
\e[H, \e[ROW;COLH, \e[…f HOME, then that many DOWNs and RIGHTs.
\e[nA … \e[nD n × UP, DOWN, RIGHT, LEFT.
\e[nG RETURN, UP, then n−1 RIGHTs (column n of the same line).
\e[…m Colour and reverse (7 / 27), see the colour table.
Other escape sequences (\e[K, \e[?25l, \e(B, …) Dropped, even when a write cuts one in two.
PETSCII control codes (the constants) Passed through.
Other C0/C1 bytes, DEL (0x7f) Dropped.

The 40-column wrap. The Encoder counts columns. A line of exactly 40 characters makes the C64 wrap by itself; the \r\n after it is then swallowed, so there is no empty line under it.

Invalid UTF-8 is scrubbed to ? before encoding.

Real bytes, from ruby -Ilib on rubbs 1.0.0:

RUBY
require 'bbs'

enc = BBS::Petscii::Encoder.new
hex = ->(s) { s.bytes.map { |b| format('%02x', b) }.join(' ') }

puts hex.(enc.encode("Hello, World!\r\n"))
# c8 45 4c 4c 4f 2c 20 d7 4f 52 4c 44 21 0d
puts hex.(enc.encode("\e[1;33mREADY.\e[0m\r\n"))
# 9e d2 c5 c1 c4 d9 2e 9b 0d          (yellow, READY., light grey)
puts hex.(enc.encode("┌──┐\r\n│ok│\r\n└──┘\r\n"))
# b0 c0 c0 ae 0d dd 4f 4b dd 0d ad c0 c0 bd 0d
puts hex.(enc.encode("Árvíztűrő — 3…\r\n"))
# c1 52 56 49 5a 54 55 52 4f 20 2d 20 33 2e 2e 2e 0d
puts hex.(enc.encode("\e[2J\e[3;5Hx"))
# 93 13 11 11 1d 1d 1d 1d 58          (CLR, HOME, 2 x DOWN, 4 x RIGHT, x)
puts hex.(enc.encode("€ ☺"))
# 3f 20 3f
puts hex.(BBS::Petscii::Encoder.new.encode('a' * 40 + "\r\nb"))
# 41 x 40, then 42: no RETURN after the full line

e2 = BBS::Petscii::Encoder.new
puts hex.(e2.encode("\e[1;3")) + ' | ' + hex.(e2.encode("2mGo"))
#  | 99 c7 4f                          (the sequence finished in the second write: light green)

BBS::Petscii.bytes and BBS::Petscii.key

Method What it does
BBS::Petscii.bytes(codes) The bytes of a string of PETSCII code points, e.g. of the constants, as a binary string. Use it to send PETSCII past the Encoder.
BBS::Petscii.key(byte) One byte from a C64 as a key, the way readkey returns keys: a Symbol, a one-character String, or nil for a byte that is not a key. readkey and readline use it on a PETSCII line. The key bytes are one table, Petscii::KEYS (byte → key); the old KEY_* constants are gone in 1.0.
RUBY
BBS::Petscii.bytes("#{BBS::Petscii::LOWERCASE}#{BBS::Petscii::LOCK_CASE}#{BBS::Petscii::CLEAR}")
# => "\x0E\b\x93"   (0e 08 93: what petscii! sends first)

[0x41, 0xc1, 0x0d, 0x14, 0x03, 0x5f, 0x85, 0x8c, 0x5c, 0xa0, 0x93].map { |b| BBS::Petscii.key(b) }
# => ["a", "A", :enter, :backspace, :escape, :escape, :f1, :f8, "£", " ", nil]

To send raw PETSCII on a switched session (skipping the Encoder), write to the socket: session.line.write(BBS::Petscii.bytes(BBS::Petscii::LOCK_CASE)).

The C64's keys

On a PETSCII line readkey returns:

C64 key Byte readkey
A letter 0x41–0x5a lowercase String ("a")
Shift + letter 0xc1–0xda (or 0x61–0x7a) uppercase String ("A")
Digits, space, punctuation 0x20–0x40, [, ] that character
£ 0x5c "£"
↑ 0x5e "^"
Shift + space 0xa0 " "
RETURN 0x0d :enter
INST/DEL 0x14 :backspace
Cursor down / up 0x11 / 0x91 :down / :up
Cursor right / left 0x1d / 0x9d :right / :left
CLR/HOME 0x13 :home
RUN/STOP 0x03 :escape
← (top left key) 0x5f :escape
F1 … F8 0x85, 0x89, 0x86, 0x8a, 0x87, 0x8b, 0x88, 0x8c :f1 … :f8
Anything else (colour keys, CLR, …) skipped

There is no :tab, no :ctrl_… and no mouse on a C64. A board that wants to work on both machines should use RETURN, the cursor keys, letters and digits, and treat :escape as "back" (RUN/STOP and ← both give it).

readline(mask:) on a PETSCII line takes the same keys, echoes in PETSCII (or the mask), and INST/DEL removes a whole character. It returns the line as UTF-8 on RETURN.

BBS::Terminal: which machine is calling

A board with two faces must know, before its first screen, whether to speak ANSI or PETSCII. BBS::Terminal (lib/bbs/terminal.rb) is that first question, shared by every board with a C64 face.

TXT
MY BOARD

1 - MODERN (ANSI/XTERM, 256 COLORS)
2 - COMMODORE 64 (PETSCII, 40 COLUMNS)

YOUR TERMINAL [2]:
  • Plain ASCII in capitals. An ANSI terminal and a C64 in either character set print it readably (in the lowercase set the capitals show as small letters). Each line names what goes on the wire.
  • The guess is the default in brackets.
  • A key only picks, RETURN takes it. A line-mode telnet sends the digit and the RETURN together, and a RETURN left on the line would land in the first screen. A changed pick prints the prompt again on a new line (the two machines go back a character differently). Backspace (or a C64's DEL, which arrives as :ctrl_t before the line is switched) clears the pick.
  • No answer in 60 seconds takes the pick, or the guess.
Constant Value Meaning
Terminal::SETTLE 1.5 Seconds guess waits for the telnet answers.
Terminal::WAIT 60 Seconds the question waits for a key before taking the default.
Terminal::FACES hash Per face: the digit, the label, and the keys that pick it.
Face Digit Keys that pick it
:modern 1 1, a, A, m, M
:petscii 2 2, c, C, p, P

Terminal.guess(session)

RUBY
BBS::Terminal.guess(session)   # => :modern or :petscii

Calls session.settle(timeout: 1.5): waits for the client's window size and drops whatever it types meanwhile. A reported window wider than 40 columns is :modern; 40 columns or less, or no report at all (a C64 behind a WiFi modem talks plain TCP), is :petscii.

Terminal.flow(title:, modern:, petscii:)

RUBY
BBS::Terminal.flow(title: 'My Board', modern: MODERN_FLOW, petscii: PETSCII_FLOW)   # => a BBS::Flow

The whole first step as a Flow, to put into config.flow:

  1. asks the question (Terminal::Question, private since 1.0: Terminal.flow is the one way to ask); a dropped line ends the call;
  2. on :petscii calls session.petscii!; on :modern calls session.enable_mouse, but only when BBS.config.mouse is on (since 0.16.1);
  3. runs that face's Flow with the same context (ctx[:session_id] and anything set before).

Mouse switching. Mouse reporting starts with an escape sequence (\e[?1002h\e[?1006h) that would be garbage on a C64. A two-faced board therefore sets config.mouse = true (the modern face wants a mouse) and config.mouse_on_connect = false (do not switch it on before the question); Terminal.flow switches it on after a modern pick.

RUBY
BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false   # the mouse's escape sequence is garbage on a C64
  config.flow = BBS::Terminal.flow(title: 'My Board', modern: MODERN, petscii: PETSCII)
end

What the line carries (a script connected without NAWS, then RETURN):

TEXT
board → caller   ff fb 03 ff fb 01 ff fd 03 ff fd 1f          telnet: WILL SGA, WILL ECHO, DO SGA, DO NAWS
                 ...1.5 s, no NAWS from the caller...
board → caller   "\r\n\r\nMY BOARD\r\n\r\n1 - MODERN ...\r\n2 - COMMODORE 64 ...\r\n\r\nYOUR TERMINAL [2]: "
caller → board   0d                                           RETURN takes the guess
board → caller   32 0d 0a                                     "2", new line
board → caller   0e 08 93                                     petscii!: LOWERCASE, LOCK_CASE, CLEAR
                 ...the C64 face, in PETSCII...

The two-faced board

One port, two faces: the caller answers Terminal's question and gets either

  • a modern face — usually an Application: windows, menubar, mouse, 80 or more columns, 256 colours; or
  • a C64 face — a 40×25 screen in PETSCII, the way the C64 boards had it: a reversed title bar, pages of text that wait for RETURN, numbered lists, single-key menus.

The two faces share the domain code (the data, the services) and nothing of the UI. The house layout, used by Feedia BBS, Alexandria BBS and the Teletype BBS:

TEXT
bbs.rb                   BBS.configure + BBS::Terminal.flow(modern:, petscii:)
lib/domain/…             the data: feeds, Wikipedia, …   (no UI)
lib/ui/modern.rb         UI::Modern.flow(...)   → a BBS::Flow that runs an Application
lib/ui/petscii.rb        UI::Petscii.flow(...)  → a BBS::Flow with one call step
lib/ui/petscii/screen.rb UI::Petscii::Screen    → the 40×25 screen helpers

Feedia, the wiring

bbs/feedia-bbs/bbs.rb (an RSS reader):

RUBY
require 'bbs'
require_relative 'lib/domain/feeds'
require_relative 'lib/domain/inbox'
require_relative 'lib/ui/modern'
require_relative 'lib/ui/petscii'

feeds = Feeds.load(ENV.fetch('FEEDIA_FEEDS', File.join(__dir__, 'config/feeds.yml')))
inbox = Inbox.new(path: ENV.fetch('FEEDIA_INBOX', File.join(__dir__, 'data/inbox.jsonl')),
                  webhook: ENV.fetch('FEEDIA_DISCORD_WEBHOOK', ''))

BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false
  config.idle_seconds     = 600

  config.flow = BBS::Terminal.flow(title: 'Feedia', modern: UI::Modern.flow(feeds, inbox),
                                   petscii: UI::Petscii.flow(feeds, inbox))
end

BBS.start

Alexandria BBS (bbs/alexandria-bbs/bbs.rb, Wikipedia on a telnet line) is the same with Wikipedia.new(...) in place of feeds and inbox, and title: 'Alexandria BBS'.

The PETSCII face: BBS::Petscii::Screen

The PETSCII face does not use Application: it writes lines, the way a C64 board did. The screen of such a board is part of rubbs, BBS::Petscii::Screen (since 0.29); each board subclasses it in lib/ui/petscii/screen.rb for methods of its own. Its ideas:

Colours as ANSI. The screen writes ANSI colours, which the PETSCII line turns into the C64's. rubbs' width helpers (FrameBuffer.wrap_ansi, the one word-wrap, and clip_ansi) know ANSI and would count PETSCII control codes as text, so ANSI keeps wrapping right. BBS::Petscii::Colors names the colours the C64 way: WH YE CY LG GR DG LB BL GN RD PU OR, and RV/RO for reverse on and off. They are the C64 names of the one palette, BBS::Ansi (YE is BBS::Ansi::BRIGHT_YELLOW, LG is BBS::Ansi::GREY, the text colour).

RUBY
module UI
  module Petscii
    BBS::Petscii::Colors.install(self)   # bare WH, YE, LG … in every class nested here

    class Screen < BBS::Petscii::Screen
      def banner(text) = puts(" #{YE}#{text}#{LG}")   # the board's own methods
    end
  end
end

Its methods, each a C64-board habit (40×25 by default; Screen.new(session, cols: 80, rows: 24) serves a modern terminal too):

Method What it does
cls Clears the screen: the C64's CLR, ANSI on a modern terminal.
title(text, right = '') A clear screen under a reversed cyan title bar, text on the left and right (a count, a language) on the right, 39 columns wide.
say(text, color: LG) Text wrapped to 38 columns, one space in. The colour is a keyword: say('Posted.', color: GN).
rule(color: DG) A horizontal line, 39 columns.
key(timeout:) readkey, raising IOError on hang-up (so a call step ends quietly).
choose(prompt, keys, back: 'q') A one-key prompt: waits for one of keys (lowercase letters), RUN/STOP and ← give back, echoes the key in capitals.
ask(prompt, text:, mask:, max:) A line typed in a BBS::Field, stripped.
edit(text = '', title:, single_line: false, max: nil) The full-screen BBS::Editor at the screen's size: the text when saved, nil when the caller quit.
pause(text = 'return: back') Waits for any key.
page(heading, lines, right:, wait:, numbers:) Lines a screenful (20 lines) at a time under the title bar; RETURN for more, q or RUN/STOP leaves early; numbers: true shows 2/5 in the bar.
pick(heading, items, empty:, right:, commands:) { label } A numbered BBS::Selector.list under the title bar: the cursor keys or the number, then RETURN; nil for q, ← or RUN/STOP.
menu(items, back:) A BBS::Selector at the cursor.
actions(*pairs) A row of keys as a cursor menu: screen.actions(%w[r read], %w[q back]).
plain(text) On a C64, BBS::Petscii.plain(text): typography and accents as ASCII, so a line wraps where it is counted.

Every menu of the screen is a BBS::Selector, in its default colours, BBS::Selector::COLORS (the roles text key bar heading about hint, in the palette's C64 names). A board that wants another footer merges into them: BBS::Selector::COLORS.merge(hint: "#{CY}#{RV}").

From 0.x to 1.0 a C64 face changes in three places: say(text, GN) and rule(DG) become say(text, color: GN) and rule(color: DG); editor(max, title) becomes edit(title: title, max: max).to_s (it returns nil when the caller quits); and Petscii::Screen::MENU is gone, so colors: MENU is dropped and MENU.merge(…) becomes BBS::Selector::COLORS.merge(…). Petscii::Screen.plain is BBS::Petscii.plain. The full list is in the gem's UPGRADING.md.

The face's flow is one call step that runs the whole call, and a second one that says goodbye; the goodbye rescues the hang-up errors, because a caller who drops during the menu should not leave a backtrace:

RUBY
def self.flow(feeds, inbox)
  BBS::Flow.define do
    call do |ctx, runner|
      Reader.new(feeds, inbox, Screen.new(runner.session), ctx).run
      nil
    end
    call do |_ctx, runner|
      C64.goodbye(Screen.new(runner.session))
      nil
    rescue IOError, Errno::EPIPE, Errno::ECONNRESET
      nil
    end
  end
end

The Reader is the board itself: a masthead, a menu of F/S/W/Q, the feeds through pick, an item through page (its Markdown rendered with BBS::Markdown.to_lines(body, width: 38, theme: {...}) in the screen's colours). Alexandria's Proxy is the same shape: a prompt that takes a title, results through pick, an article's sections through page.

Drawing for 40 columns. Logos are drawn from reversed spaces (RV + spaces + RO), which show the same on both machines; box drawing and █ are fine too, the Encoder maps them. The screen keeps its title bar and rules to 39 columns and its text to 38; a line of exactly 40 also works, since the Encoder swallows the RETURN after the C64's own wrap.

A complete two-faced board

A small, runnable board: news items, an Application on modern terminals, rubbs' BBS::Petscii::Screen on a C64.

RUBY
require 'bbs'

NEWS = [
  ['Board opens', 'The board is open for callers from today. Call often, and tell your friends.'],
  ['PETSCII face', 'A Commodore 64 gets the same news on 40 columns, in PETSCII.']
].freeze

# The modern face: an Application with a mouse.
MODERN_APP = BBS::Application.define do
  theme BBS::Theme.synchronet
  body do
    NEWS.each_with_index do |(title, text), i|
      add BBS::Widgets::Label.new(text: "#{title}: #{text}", style_key: :text,
                                  bounds: BBS::Widget::Rect.new(3, 3 + i, 74, 1))
    end
  end
  status_hint('F10', 'Log off') { :halt }
end

MODERN = BBS::Flow.define do
  app MODERN_APP
end

# The PETSCII face: a 40x25 screen, the news as a numbered cursor menu.
PETSCII = BBS::Flow.define do
  call do |_ctx, runner|
    screen = BBS::Petscii::Screen.new(runner.session)
    while (item = screen.pick('my board', NEWS, right: 'q: log off', &:first))
      title, text = item
      screen.title(title)
      screen.say(text)
      screen.pause
    end
    nil
  end
end

BBS.configure do |config|
  config.mouse            = true
  config.mouse_on_connect = false
  config.idle_seconds     = 600
  config.flow = BBS::Terminal.flow(title: 'My Board', modern: MODERN, petscii: PETSCII)
end

BBS.start

On the C64 side, the first screen after RETURN starts like this (rubbs 1.0):

TEXT
0e 08 93            petscii!: lowercase set, case locked, clear
93 9f 12 20 4d 59 20 42 4f 41 52 44 20 … 51 3a 20 4c 4f 47 20 4f 46 46 20 92 9b
                    CLEAR, cyan, RVS ON, " my board … q: log off ", RVS OFF, light grey
9f 12 20 31 20 c2 4f 41 52 44 20 4f 50 45 4e 53 20 … 92 9b 0d
                    the lit item: cyan, RVS ON, " 1 Board opens", RVS OFF
9e 20 32 20 9b d0 c5 d4 d3 c3 c9 c9 20 46 41 43 45 … 0d
                    yellow " 2 ", light grey "PETSCII face"

Other ways to have a PETSCII face

  • A Flow drawn for 40 columns. The Impostor BBS runs the same BBS::Flow on both faces, the PETSCII face with its ERB screens drawn for 40 columns in screens/petscii/: Impostor::Petscii.flow(sessions: store).
  • A FakeOS. A BBS::FakeOS whose screens fit 40 columns runs on a PETSCII line unchanged.
  • A door. A door drawn for 40 columns works from a C64 face; a BBS::Gateway passes a PETSCII caller on to another board, and since it reports no window size, a two-faced board on the other side guesses C64 (see Doors).

See also