---
title: Doorok
url: 'https://wiki.teletypegames.org/hu/engines/rubbs/doors'
markdown: 'https://wiki.teletypegames.org/hu/engines/rubbs/doors.md'
lang: hu
date: '2026-10-27'
description: 'Minden, amivel a rubbs a hívó telnetvonalát átadja valami másnak, majd visszaveszi: a doorok (BBS::Door), a szimulált gépek, amikbe a hívó gépelhet (BBS::FakeOS), a telnet-gatewayek más boardokhoz (BBS::Gateway), és a ZMODEM-letöltés a hívó termináljára (BBS::Zmodem). Az oldal minden példáját a ru…'
taxonomy:
  tag:
    - reference
---

# Doorok

> Minden, amivel a [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs) a hívó telnetvonalát átadja valami másnak, majd visszaveszi: a doorok (`BBS::Door`), a szimulált gépek, amikbe a hívó gépelhet (`BBS::FakeOS`), a telnet-gatewayek más boardokhoz (`BBS::Gateway`), és a ZMODEM-letöltés a hívó termináljára (`BBS::Zmodem`). Az oldal minden példáját a rubbs 0.17.0-n futtattuk, a szöveg a 0.17.1-hez igazítva.

# Mi az a door

A régi boardokon a door egy külön program volt, amibe a hívó átlépett: a BBS átadta a vonalat, a program közvetlenül beszélt a hívóval, és amikor kilépett, a vonal visszakerült a boardhoz. A rubbsban a door egy `BBS::Door`, ami háromféle programot tud becsomagolni:

| Program | Mi ez | Hogyan futtatja a door | Hogyan lép ki a hívó |
|---|---|---|---|
| `BBS::FakeOS`-definíció | szimulált gép: bootszkript és parancsértelmező | `FakeOS::Runner.new(session, definition, context: context).run` | egy parancs, ami `io.halt`-ot hív (EXIT, BYE, …) |
| `BBS::Flow` | soronkénti párbeszéd | `FlowRunner` a context **másolatán**, utána „Press ENTER to return to the BBS...” | a flow véget ér, aztán ENTER |
| bármi, aminek van `call(session, context)` metódusa | lambda, metódusobjektum, saját osztály, `BBS::Gateway` | `program.call(session, context)` | a metódus visszatér |

Egy door oda-vissza útja egy applicationből:

```mermaid
sequenceDiagram
  participant C as Hívó
  participant A as Application
  participant B as Builder#run
  participant D as Door#run
  participant P as Program
  C->>A: Doors > TT-80
  A->>A: open_door(:tt80) beállítja a context[:door]-t, stop!
  A-->>B: véget ér az eseményciklus
  B->>B: on_door blokk (door, context)
  B->>D: door.run(session, context)
  D->>C: egér ki, színek alaphelyzetbe, képernyőtörlés
  D->>P: elindítja a programot
  P->>C: közvetlenül beszél a hívóval
  C->>P: EXIT
  P-->>D: visszatér
  D->>C: egér vissza (ha BBS.config.mouse)
  D-->>B: visszatér
  B->>A: újraépíti az applicationt (ugyanazzal a contexttel)
  A->>C: az asztal, újrarajzolva
```

A legkisebb door egy lambda, egy flow-ból indítva:

```ruby
require 'bbs'

DICE = BBS::Door.new(:dice, lambda { |session, ctx|
  session.write("Rolling for #{ctx[:username]}... #{rand(1..6)}\r\nPress a key.")
  session.readkey
}, label: '&Dice')

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    ask :username, prompt: 'Your name'
    call do |ctx, runner|
      DICE.run(runner.session, ctx)
      runner.write "\r\nBack on the board.\r\n"
      nil
    end
  end
end

BBS.start
```

# BBS::Door

Forrás: `lib/bbs/door.rb`.

## Door.new

```ruby
BBS::Door.new(name, program, label: nil)
```

| Argumentum | Alapérték | Jelentés |
|---|---|---|
| `name` | kötelező | a door neve; Symbolként tárolja (`door.name`) |
| `program` | kötelező | egy `BBS::FakeOS::Definition`, egy `BBS::Flow`, vagy bármilyen objektum, aminek van `call` metódusa |
| `label:` | `name.to_s.upcase` | a menüpont szövege; az `&` jelöli a gyorsbillentyűt, mint a `'&SERMO'`-ban |

Minden más `ArgumentError`-t dob („not a FakeOS definition or a callable”). Olvasható: `name`, `label`, `program`.

## Door#run

```ruby
door.run(session, context = {})
```

Sorrendben ezt csinálja:

1. `session.disable_mouse`, hogy a program ne kapjon egérjelentéseket, amiket nem tud olvasni.
2. Kiírja: `ESC[0m ESC[2J ESC[H` — alapszínek, üres képernyő, kurzor a bal felső sarokban.
3. Elindítja a programot a fajtája szerint (lásd a fenti táblázatot).
4. Utána, bármi történt: `session.enable_mouse`, ha a `BBS.config.mouse` be van kapcsolva.

Ha a program elhasal, `nil`-t ad vissza (lásd lent); különben azt, amit a program.

### A context

A `context` hash a hívó állapota az egész hívás alatt. A FakeOS- és a callable door **ugyanazt a hasht** kapja: amit a door beleír, az visszatérés után is ott marad, így egy door a hívás végéig megtarthatja az állapotát (AI-chat előzményeit, egy meghajtóbetűt, egy pontszámot), és a board utána ki is olvashatja.

A `BBS::Flow` door ehelyett `context.dup`-ot kap, hogy az `ask` lépései ne írhassák felül a board saját kulcsait (`:username`, `:authenticated`, …). A flow-futtatónak kell a `context[:session_id]` is; applicationön belül ez már ott van, önálló használatnál add át. Ha a flow saját ERB-screenekkel jön, adj neki `screens_dir:`-t (lásd [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow)). A flow-nak nincs saját kilépő parancsa, ezért amikor véget ér, a door kiírja: „Press ENTER to return to the BBS...”, és vár egy sorra.

### Hibák és bontás

Egy door hibája nem bonthatja a hívást:

| Mi történik a programban | Eredmény |
|---|---|
| bármilyen `StandardError` (bug, sikertelen lekérdezés) | `BBS.log`-gal naplózza `door NÉV` címkével (osztály, üzenet, a backtrace három sora); a `run` `nil`-t ad, a hívó visszakerül a boardra |
| `IOError` vagy `SystemCallError` (leszakadt a vonal) | továbbdobja: a hívás véget ér, ahogy kell |
| egy FakeOS vagy Flow program alatt szakad le a vonal | a futtatója lenyeli az `IOError`-t, `EPIPE`-t és `ECONNRESET`-et, és visszatér; a board a következő olvasásnál veszi észre a bontást |

```ruby
require 'bbs'

broken = BBS::Door.new(:broken, ->(_session, _ctx) { raise 'boom' })
broken.run(session, ctx)   # naplózza: "my-bbs: door broken: RuntimeError: boom", nil-t ad

dropped = BBS::Door.new(:dropped, ->(_session, _ctx) { raise IOError, 'line dropped' })
dropped.run(session, ctx)  # IOError-t dob: a hívás véget ér
```

## Doorok egy applicationben

Az [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application) DSL-je deklarálja a doorokat, menübe teszi őket, és bármelyik handlerből megnyitja. Forrás: `lib/bbs/application.rb`.

| DSL / metódus | Hol | Mit csinál |
|---|---|---|
| `door(name, program, label: nil)` | `Application.define` | deklarál egy doort: `Door.new(name, program, label: label)`, deklarációs sorrendben |
| `doors` | `menu(...) { }` belsejében | minden deklarált doorhoz egy menüpont `door.label` felirattal, mindegyik `open_door(door.name)`-et hív |
| `open_door(name)` | a futó applicationön (menüpontok, státuszsor-hintek, billentyűhandlerek, window-gombok, lista-callbackek, helperek) | a doort a `context[:door]`-ba teszi, a most kezelt esemény után leállítja az eseményciklust, és `:halt`-ot ad; ismeretlen névre `ArgumentError` |
| `on_door { \\|door, context\\| }` | `Application.define` | közvetlenül azelőtt fut, hogy a vonal a doorhoz kerül: bejegyzés a node-listába, egy naplósor |
| `Builder#run(session:, session_id:, context: {})` | a definiált application | felépíti és futtatja az applicationt; ha egy door miatt ért véget, futtatja a doort, és újraépíti az applicationt; akkor tér vissza, ha a hívó door nélkül lép ki, vagy megszűnik a vonal (ilyenkor a függő doort nem nyitja meg) |
| `Application#doors` | a futó application | a deklarált doorok név szerinti Hashben |

Az `open_door` a `Builder#run`-on át működik, és (0.17.1 óta) az `app BOARD` flow-lépés is ezt használja. A sima `BOARD.build(...).run` egyszer futtatja az eseményciklust, és megáll: ott az `open_door` csak befejezi az applicationt. Doorokkal rendelkező applicationt egy flow `app` lépéséből indíts, vagy `call` lépésből, ha kód is kell köré, mint itt:

```ruby
require 'bbs'
require_relative 'tt80' # a TT-80 FakeOS a lenti teljes példából

QUIZ = BBS::Flow.define do
  big_banner 'Quiz', font: 'small'
  ask :answer, prompt: 'What is 6 x 7'
  call do |ctx, runner|
    runner.write(ctx[:answer] == '42' ? "  Right!\r\n" : "  No, it is 42.\r\n")
    nil
  end
end

DICE = lambda do |session, ctx|
  session.write("Rolling for #{ctx[:username]}... #{rand(1..6)}\r\nPress a key.")
  session.readkey
end

BOARD = BBS::Application.define do
  door :tt80, TT80, label: '&TT-80'
  door :quiz, QUIZ, label: '&Quiz'
  door :dice, DICE, label: '&Dice'

  menubar do
    menu('&Doors') { doors }
  end

  status_hint('F5', 'Dice') { open_door(:dice) }
  status_hint('Alt-X', 'Bye') { :halt }

  on_door do |door, ctx|
    warn "#{ctx[:username]} enters #{door.name}"
  end
end

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    ask :username, prompt: 'Your name'
    call do |ctx, runner|
      BOARD.run(session: runner.session, session_id: ctx[:session_id], context: ctx)
      nil
    end
  end
end

BBS.start
```

Az F5 dob a kockával, az Alt-D megnyitja a Doors menüt a TT-80, Quiz és Dice pontokkal. A kvíz `:answer` kulcsa a kvíz contextmásolatában marad; a TT-80 meghajtóbetűje a board contextjében.

A Teletype BBS (`bbs/teletype-bbs`) adatként tartja a doorjait (`TeletypeBBS#doors` a `lib/domain/teletype_bbs.rb`-ben: név, program, felirat, a legkeskenyebb szélesség, amin elfér, és opcionálisan egy C64-es változat); a modern arc ciklusban deklarálja őket, az `on_door` pedig „Door: SERMO”-t ír a hívó node-jához a Who is Online-ban:

```ruby
bbs.doors.each { |d| door d.name, d.program, label: d.label }

on_door do |door, ctx|
  bbs.online.activity(ctx[:session_id], "Door: #{door.label.delete('&')}")
end
```

## Door application nélkül

A `Door#run`-nak csak egy session és egy context kell. Egy flow `call` lépése, egy PETSCII-képernyő vagy saját kód közvetlenül is megnyithatja, mint az oldal első példájában. A Teletype BBS PETSCII-arcának (`lib/ui/petscii/board.rb`) nincs applicationje, és pontosan így csinálja; ha a doornak van 40 oszlopos változata, azt választja:

```ruby
BBS::Door.new(door.name, door.petscii || door.program, label: door.label).run(@screen.session, @ctx)
```

`Door` nélkül egy flow helyben is futtathat FakeOS-t az `os` lépéssel. Ez a flow contextjén osztozik, de nincs képernyőtörlés, egérkezelés és hibavédelem:

```ruby
BBS.config.flow = BBS::Flow.define do
  ask :username, prompt: 'Your name'
  os TT80
  say 'Back on the board.'
end
```

# BBS::FakeOS

Forrás: `lib/bbs/fake_os.rb`. Teletype-stílusú szimulált gép: egy szkriptelt bootfolyamat, aztán egy parancsértelmező. Minden szimulált OS csak egy újabb definíció; a motor általános, így egy új gép konfiguráció, nem új motorkód.

## Egy definíció váza

```ruby
require 'bbs'

DOS = BBS::FakeOS.define do
  name   'MS-DOS'
  prompt 'C:\\>'
  colors text: "\e[0;37m", accent: "\e[1;32m"

  boot do
    print 'Award Modular BIOS v4.51PG', delay: 0.4
    memtest 16_384
    pause 0.5
    cls
    big_banner 'MS-DOS'
  end

  ready { puts 'Type VER, CLS or EXIT.' }

  command('ver')          { |_args, io| io.puts 'MS-DOS Version 6.22' }
  command('cls')          { |_args, io| io.cls }
  command('exit', 'quit') { |_args, io| io.halt }

  on_unknown do |_input, io|
    io.puts 'Bad command or file name'
  end
end
```

A `BBS::FakeOS.define { ... }` a blokkot egy új `FakeOS::Definition`-ön értékeli ki, és azt adja vissza. Egy definícióban kétféle blokk van, és mást látnak:

- A `boot` és a `ready` **a konzolon** fut (`instance_eval`): a `print`, `puts`, `cls`, `ctx`, `session` és a többi fogadó nélkül működik. Vigyázat: itt a `puts` és a `print` a hívónak ír, nem a szerver stdoutjára; naplózni `warn`-nal lehet.
- A `command` és az `on_unknown` blokkok **argumentumokat kapnak**: `(args, io)` és `(input, io)`. Minden kimenethez és állapothoz az `io`-t használd.

## A definíciós DSL

| Metódus | Alapérték | Mit csinál |
|---|---|---|
| `name(value = nil)` | `'OS'` | beállítja a nevet; argumentum nélkül visszaadja. Csak tájékoztató: a shell nem írja ki |
| `prompt(value = nil, &block)` | `'> '` | String, vagy egy blokk / lambda, ami a contextet kapja és a promptot adja, minden sor előtt kiértékelve; argumentum nélkül a mostanit adja vissza. A `''` is megengedett (a Commodore-doorok maguk írják ki a `READY.`-t) |
| `colors(overrides = nil)` | `DEFAULT_COLORS` | SGR-sztringeket fésül a színszerepekbe; argumentum nélkül a szerepek Hashét adja |
| `boot { }` | nincs | a bekapcsolási szkript, egyszer fut a konzolon |
| `ready { }` | nincs | egyszer fut a `boot` után, az első prompt előtt |
| `command(*names, help: nil) { \\|args, io\\| }` | nincs | parancsot regisztrál egy vagy több néven; az első a kanonikus |
| `on_unknown { \\|input, io\\| }` | „Bad command or file name”-et ír | mi legyen azzal a bemenettel, ami egyik parancsra sem illik |

A `boot_block`, `ready_block`, `commands`, `order` és `unknown` olvasók mutatják, mi lett deklarálva. A `commands` minden kisbetűs nevet (az aliasokat is) `{ name:, help:, block: }`-ra képez; az `order` a kanonikus neveket sorolja deklarációs sorrendben. Egy HELP parancs ezekből rakja össze a listáját (lásd a teljes példát).

Egy prompt, ami követi a gép állapotát:

```ruby
prompt { |ctx| "#{ctx[:drive] || 'A'}> " }
# vagy
prompt ->(ctx) { "#{ctx[:drive] || 'A'}> " }
```

## Színszerepek és a `base`

Minden kimeneti primitív kap egy `style:` szerepet. Ismeretlen szerep esetén a `:text` színe érvényes.

| Szerep | Alapérték | Ki használja |
|---|---|---|
| `text` | `"\e[0;37m"` (világosszürke) | `puts`, `print`, `type`, `memtest` |
| `bright` | `"\e[1;37m"` | a saját kiemeléseid |
| `accent` | `"\e[1;32m"` | `banner`, `big_banner` |
| `error` | `"\e[1;31m"` | „Bad command or file name”, „Error: …” |
| `dim` | `"\e[1;30m"` | a halk szövegeid (AI-válaszok, tippek) |
| `prompt` | `"\e[0;37m"` | a prompt |
| `base` | `''` | ide tér vissza minden reset |

Minden kimenet után a konzol `ESC[0m`-et ír, majd a `base`-t, és a `cls` is ezt írja a képernyőtörlés előtt. Ha tehát a `base` hátteret állít, az a háttér túléli a promptot, a hívó visszhangját és az új sorokat, a `cls` pedig az egész képernyőt kitölti vele. A többi szerepnek is add meg ugyanazt a hátteret: a `0;`-val kezdődő SGR törli.

```ruby
colors base:   "\e[44m",          # kék képernyő
       text:   "\e[0;37;44m",
       bright: "\e[0;97;44m",
       error:  "\e[0;91;44m",
       prompt: "\e[0;97;44m"
```

## A konzol

A `FakeOS::Console`-é a session és a kimenet. A `boot`-ban és a `ready`-ben ez a `self`, a parancsokban az `io` argumentum.

| Metódus | Mit csinál |
|---|---|
| `puts(text = '', style: :text)` | egy sor, CRLF-fel a végén |
| `print(text = '', delay: 0, style: :text, newline: 1)` | szöveg, utána `newline` darab CRLF, aztán `delay` másodperc szünet; `newline: 0` a sorban hagyja a kurzort |
| `type(text, style: :text, cps: 900, newline: 1)` | írógép: karakterenként, másodpercenként `cps` karakterrel (`cps: 0` egyben írja ki) |
| `newline(count = 1)` | `count` darab CRLF |
| `cls` | alaphelyzetbe (a `base`-re) állítja a színeket, és törli a képernyőt |
| `pause(seconds)` | alszik |
| `banner(text, style: :accent)` | keretes egysoros banner (`BBS::Banner.render`) |
| `big_banner(text, style: :accent, font: 'standard')` | figlet-banner az artii-n át (`'standard'`, `'small'`, `'slant'`, …) |
| `memtest(kilobytes, step: 2048, delay: 0.03, label: 'Memory Test : ')` | a számláló memóriateszt, helyben egy soron, a végén „64K OK” |
| `cols`, `rows` | a hívó ablakmérete (telnet NAWS), alapból 80 és 24 |
| `halt` | az aktuális parancs után befejezi a shellt |
| `ctx` | a context hash (a door contextje) |
| `session` | a `BBS::Session` a `readline`-hoz, `readkey`-hez, `write`-hoz |
| `definition` | a futó definíció |

## A shell ciklusa

A `boot` és a `ready` után a konzol `halt`-ig vagy bontásig ismétli:

1. Kiírja a promptot a `prompt` szerepben.
2. Beolvas egy sort (`session.readline`); a `nil`, vagyis a bontás befejezi a ciklust.
3. Levágja a szóközöket; üres sorra csak újra promptol.
4. Leválasztja az első szót, kisbetűsíti, és megkeresi a `commands`-ban, így a parancsok kis- és nagybetűtől függetlenek. A blokk Stringként kapja a sor maradékát (`''`, ha nincs), és a konzolt.
5. Ha nincs találat: az `on_unknown` megkapja az egész levágott sort; `on_unknown` nélkül „Bad command or file name” az `error` szerepben.

Egy parancsban vagy az `on_unknown`-ban keletkező `StandardError`-t naplózza (`my-bbs: fake os command: …`), a hívónak „Error: ÜZENET”-ként mutatja az `error` szerepben, és a shell megy tovább. Az `IOError` és a `SystemCallError` továbbmegy. A `boot`-ban vagy a `ready`-ben keletkező kivételt a konzol nem kapja el: doorban ez visszaviszi a hívót a boardra.

**Nincs beépített EXIT vagy HELP.** Regisztráld magad; az EXIT `io.halt`-ot hív:

```ruby
command 'exit', 'quit', 'bye', help: 'back to the BBS' do |_args, io|
  io.puts 'Goodbye.'
  io.halt
end
```

Egy parancs a sessionön át saját kis párbeszédet is folytathat:

```ruby
command 'name', help: 'asks for your name' do |_args, io|
  io.print 'NAME? ', newline: 0
  name = io.session.readline.to_s.strip
  io.puts "HELLO, #{name.upcase}."
end
```

## Egy FakeOS futtatása

| Hogyan | Context | Plusz |
|---|---|---|
| `BBS::Door.new(:name, DEFINITION).run(session, ctx)` | ugyanaz a hash | képernyőtörlés, egér ki és be, hibavédelem |
| `door :name, DEFINITION` egy applicationben | az application contextje | mind a fenti, plusz a menü és az `on_door` |
| `os DEFINITION` lépés egy flow-ban | a flow contextje | nincs |
| `BBS::FakeOS::Runner.new(session, DEFINITION, context: {}).run` | a megadott hash | nincs; bontáskor `nil`-t ad |

## Egy teljes gép: a TT-80

Egy kitalált 1980-as mikroszámítógép kék képernyővel, bootfolyamattal, az aktuális meghajtót mutató prompttal, generált HELP-pel, egy argumentumos paranccsal, egy saját kérdést feltevővel, egy szándékosan elhasalóval, és EXIT-tel. Mentsd `tt80.rb` néven:

```ruby
require 'bbs'

TT80 = BBS::FakeOS.define do
  name   'TT-80'
  prompt ->(ctx) { "#{ctx[:drive] || 'A'}> " }

  blue = "\e[44m"
  colors base:   blue,
         text:   "\e[0;37;44m",
         bright: "\e[0;97;44m",
         accent: "\e[0;93;44m",
         error:  "\e[0;91;44m",
         dim:    "\e[0;36;44m",
         prompt: "\e[0;97;44m"

  boot do
    cls
    print 'TT-80 ROM MONITOR V1.3', delay: 0.2
    memtest 64, step: 16, delay: 0.02, label: 'RAM CHECK '
    pause 0.3
    cls
    banner 'TT-80 DISK OPERATING SYSTEM'
    type 'Insert a disk, or just type.', style: :dim, cps: 120
    ctx[:drive] ||= 'A'
  end

  ready do
    puts "#{session.term_cols}x#{session.term_rows} screen. HELP lists the commands.", style: :dim
    newline
  end

  command 'help', '?', help: 'this list' do |_args, io|
    io.definition.order.each do |name|
      entry = io.definition.commands[name]
      io.puts format('  %-6s %s', name.upcase, entry[:help]), style: :bright
    end
  end

  command 'dir', help: 'list the disk' do |_args, io|
    io.puts "  #{io.ctx[:drive]}: GAMES    BAS   #{io.ctx[:drive]}: README   TXT"
  end

  command 'drive', help: 'DRIVE B switches the drive' do |args, io|
    letter = args.strip.upcase
    if letter.match?(/\A[AB]\z/)
      io.ctx[:drive] = letter
    else
      io.puts 'DRIVE A or DRIVE B', style: :error
    end
  end

  command 'name', help: 'asks for your name' do |_args, io|
    io.print 'NAME? ', newline: 0
    name = io.session.readline.to_s.strip
    io.puts "HELLO, #{name.upcase}."
  end

  command 'size', help: 'the screen size' do |_args, io|
    io.puts "#{io.cols} COLUMNS, #{io.rows} ROWS"
  end

  command 'crash', help: 'a broken command' do |_args, _io|
    raise 'DISK NOT READY'
  end

  command 'exit', 'bye', help: 'back to the BBS' do |_args, io|
    io.puts 'SYSTEM HALTED.'
    io.halt
  end

  on_unknown do |input, io|
    io.puts "#{input.split.first.upcase}? SYNTAX ERROR", style: :error
  end
end
```

És egy board, ami doorként nyitja meg, `bbs.rb`:

```ruby
require 'bbs'
require_relative 'tt80'

TT80_DOOR = BBS::Door.new(:tt80, TT80, label: '&TT-80')

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    ask :username, prompt: 'Your name'
    call do |ctx, runner|
      TT80_DOOR.run(runner.session, ctx)
      runner.write "\r\nBack on the board, #{ctx[:username]}. Drive was #{ctx[:drive]}.\r\n"
      nil
    end
  end
end

BBS.start
```

Egy hívás 100×30-as terminállal:

```text
TT-80 ROM MONITOR V1.3
RAM CHECK 64K OK

  ╔═══════════════════════════════╗
  ║  TT-80 DISK OPERATING SYSTEM  ║
  ╚═══════════════════════════════╝

Insert a disk, or just type.
100x30 screen. HELP lists the commands.

A> help
  HELP   this list
  DIR    list the disk
  DRIVE  DRIVE B switches the drive
  NAME   asks for your name
  SIZE   the screen size
  CRASH  a broken command
  EXIT   back to the BBS
A> drive b
B> crash
Error: DISK NOT READY
B> foo bar
FOO? SYNTAX ERROR
B> exit
SYSTEM HALTED.

Back on the board, Zsolt. Drive was B.
```

## FakeOS élesben

**SERMO** (`bbs/sermo-bbs`, `sermo` gem, `lib/sermo/sermo_os.rb`): zöld foszforos gép a Neumatronic-univerzumból. A bootfolyamat egy oldalnyi `print ..., delay:` sor és egy saját `label:`-ű `memtest`; tizenegy szó szerinti parancs (`VER`, `DIR`, `MEM`, …) a „digitális oldal”, minden mást az `on_unknown` egy AI-karakternek küld a `BBS::AI::Typewriter`-en át (lásd [AI-karakterek](https://wiki.teletypegames.org/hu/engines/rubbs/ai)). A gem csak a definíció, a `Sermo::OS`; a gazdaboard futtatja:

```ruby
require 'sermo'

BBS::FakeOS::Runner.new(session, Sermo::OS, context: ctx).run
# vagy egy applicationben:
door :sermo, Sermo::OS, label: '&SERMO'
```

**Commodore 64 és Plus/4** (`bbs/teletype-bbs`, `lib/doors/commodore.rb`): egyetlen `Commodore.os(machine, games:, sizes:)` épít FakeOS-t bármelyik géphez. Maga a definíció apró; a gép egy `Shell` objektumban él, amit a context tart:

```ruby
BBS::FakeOS.define do
  name   machine.platform
  prompt ''                      # a BASIC maga írja ki a READY.-t
  colors machine.colors          # base: "\e[94;44m" a C64 kék képernyőjéhez

  boot do
    ctx[:commodore] = Shell.new(self, machine, games: games, files: files)
    ctx[:commodore].power_on
  end

  on_unknown { |input, io| io.ctx[:commodore].enter(input) }
end
```

Egyetlen `command` sincs benne: minden sor a shellhez megy, ami értelmezi a `LOAD"$",8`, `LIST`, `RUN` parancsokat és a BASIC-sorokat, maga tördeli a kimenetet 40 oszlopra, és EXIT-re `io.halt`-ot hív. Egy futó BASIC-program `io.session.readkey(timeout: 0)`-val figyel, így bármelyik billentyű RUN/STOP-ként működik.

**Chat of Ra** (`lib/doors/ra.rb`): napisten-karakter, a játéklogika parancsokban (`RIDDLE`, `HINT`, `SKIP`, `GAME`), a szabad beszélgetés az `on_unknown`-on át a modellhez megy.

# BBS::Gateway

Forrás: `lib/bbs/gateway.rb` és `lib/bbs/gateway/`, 0.17.0 óta; a jelentés és a `connect_timeout:` a 0.31.0-ban jött, a `translate:` a 0.34.0-ban, a `public_only:` az 1.0.0-ban. Door egy másik telnetes boardhoz, mint a régi BBS-ek telnet-gatewayei: a hívó vonala változatlanul továbbmegy a másik boardhoz, és akkor jön vissza, amikor az bont. A másik board a saját kódját futtatja a saját szerverén; a gateway csak a bájtokat viszi.

## Gateway.new

```ruby
BBS::Gateway.new(host:, port:, name: nil, terminal: 'XTERM-256COLOR', connect_timeout: 5, idle: nil,
                 translate: false, public_only: true)
```

| Opció | Alapérték | Jelentés |
|---|---|---|
| `host:` | kötelező | a másik board hosztneve vagy címe |
| `port:` | kötelező | a telnetportja (`to_i`-vel alakítva) |
| `name:` | `"HOST:PORT"` | a név a „Connecting to …”, a „… hung up” üzenetekben és a naplóban |
| `terminal:` | `'XTERM-256COLOR'` | a jelentett termináltípus, ha a másik board rákérdez (telnet TTYPE) |
| `connect_timeout:` | 5 | ennyi másodpercig vár, hogy a másik board fogadja a kapcsolatot |
| `idle:` | `nil` | ennyi másodperc kétirányú csend után a hívás úgy ér véget, mintha a hívó bontott volna; `nil` esetén `BBS.config.idle_seconds` (ha az sincs, 900 s) |
| `translate:` | `false` | egy PETSCII-hívó egy `AsciiBridge`-en át kapja a másik boardot (lásd *Egy C64 hív egy ASCII-boardot*); a modern hívó vonalát sosem fordítja |
| `public_only:` | `true` | csak a nyilvános interneten lévő címet hív (`BBS::Address.public?`); megbízható szomszédhoz, például a `host.docker.internal`-hoz `false` |

| Konstans | Érték | Jelentés |
|---|---|---|
| `Gateway::CONNECT_TIMEOUT` | 5 | a `connect_timeout:` alapértéke |
| `Gateway::BACK_WAIT` | 30 | ennyi másodpercig vár a hívó billentyűjére, mielőtt mindenképp visszaveszi a vonalat |

Olvasható: `host`, `port`, `name`. A gateway callable — `call(session, context = {})` —, így bármelyik programhoz hasonlóan door lehet belőle:

```ruby
door :feedia, BBS::Gateway.new(host: 'feedia.example', port: 2327, name: 'Feedia'), label: '&Feedia'
```

## Csak nyilvános címek

Az 1.0.0 óta a gateway egyszer oldja fel a hostot (IPv4-címre, ha van ilyen), a `BBS::Address.public?`-kal ellenőrzi a címet, és erre az ellenőrzött címre kapcsolódik. A loopback, privát, link-local, CGNAT és ULA címeket elutasítja: egy listázás, amely `127.0.0.1`-et vagy egy konténert ad meg, nem teheti a boardot a saját hálózatába vezető átjáróvá. A hívó ezt olvassa: „NAME may not be called from here.”, a jelentés `error`-ja pedig `BBS::Gateway::Refused`.

Az a board, amely szándékosan a saját szomszédait tárcsázza, ezt ki is mondja:

```ruby
FEEDIA = BBS::Gateway.new(host: 'host.docker.internal', port: 2327, name: 'Feedia', public_only: false)
```

## Jelentés minden hívásról

Egy sok boardot tárcsázó telefonkönyv tudni akarja, hogyan sikerült egy-egy hívás. Blokkal a `call` a hívás végén átad egy `BBS::Gateway::Report`-ot (akkor is, ha a hívó közben bontott):

| Mező | Jelentés |
|---|---|
| `connected` | fogadta-e a másik board a kapcsolatot |
| `error` | ami miatt nem jött létre: `SocketError` (a név nem oldható fel), `BBS::Gateway::Refused`, `Errno::ECONNREFUSED`, időtúllépés; `nil`, ha létrejött |
| `address` | a hívott IP-cím |
| `connect_seconds` | mennyi ideig tartott a kapcsolódás |
| `received` | a másik board által küldött bájtok, a telnet-parancsok nélkül |
| `hung_up` | ki zárta le az átkelést: `:board` vagy `:caller` |

```ruby
BBS::Gateway.new(host: board.host, port: board.port, name: board.name, connect_timeout: 10).call(session) do |report|
  record(board, report.connected ? 'ok' : report.error.class.name)
end
```

## Egy C64 hív egy ASCII-boardot

A gateway érintetlenül viszi a bájtokat, így segítség nélkül egy Commodore 64 csak PETSCII-ül beszélő boardot hívhat. `translate: true`-val a PETSCII-hívó vonala egy `BBS::Gateway::AsciiBridge`-en megy át (0.34.0 óta):

- a board szövege (UTF-8, vagy CP437, ha kiderül, hogy nem UTF-8) a PETSCII-kódolón át ér a C64-hez: a betűk a C64 kis-nagybetűjével, az ANSI-színek C64-színekként, a vonalrajz a legközelebbi jellel;
- a C64 billentyűi úgy jutnak a boardhoz, ahogy egy ANSI-terminál küldené őket: a RETURN CR-ként, a DEL backspace-ként, a kurzorbillentyűk `ESC [ A`…-ként;
- a híd mögött a C64 a TTYPE-ra `ANSI`-t, a NAWS-ra a 40×25-ös méretét válaszolja, így a board 40 oszlopra formáz.

```ruby
BBS::Gateway.new(host: board.host, port: board.port, translate: !board.petscii?).call(session)
```

## Egy átkelés

```mermaid
sequenceDiagram
  participant C as Hívó
  participant G as Gateway (ez a board)
  participant R as Másik board
  C->>G: kiválasztja a doort
  G->>C: Connecting to NAME…
  G->>R: TCP-kapcsolat (5 s)
  R->>G: IAC DO NAWS, IAC DO TTYPE, IAC WILL ECHO …
  G->>R: IAC WILL NAWS + a hívó mérete, IAC WILL TTYPE, IAC DO ECHO …
  R->>G: IAC SB TTYPE SEND
  G->>R: IAC SB TTYPE IS XTERM-256COLOR
  loop amíg valamelyik oldal nem bont
    R->>G: képernyőbájtok
    G->>C: változatlanul továbbadva
    C->>G: billentyűk, egérjelentések
    G->>R: változatlanul továbbadva
    C->>G: IAC SB NAWS (átméretezett ablak)
    G->>R: IAC SB NAWS (az új méret)
  end
  R--xG: bont
  G->>C: NAME hung up. Press a key to return.
  C->>G: egy billentyű
  G-->>C: vissza ezen a boardon
```

## A telnetkliens-oldal

A másik board felé a gateway a telnetkliens. Minden (parancs, opció) párra egyszer válaszol, így egy újra kérdező board sem okoz körbeforgást:

| A másik board küldi | A gateway válasza |
|---|---|
| `WILL ECHO`, `WILL SGA` | `DO` |
| `WILL` bármi más | `DONT` |
| `DO NAWS` | `WILL NAWS`, utána a hívó ablakmérete; PETSCII-vonalon `WONT NAWS` |
| `DO SGA` | `WILL SGA` |
| `DO TTYPE` (24-es opció) | `WILL TTYPE`; PETSCII-vonalon `WONT TTYPE` |
| `WILL BINARY`, `DO BINARY` | elfogadja, és ugyanezt kéri a hívó termináljától is, így egy `BBS::Xmodem`-átvitel a gatewayen át is működik |
| `DO` bármi más | `WONT` |
| `WONT`, `DONT` | semmi |
| `SB TTYPE SEND` | `SB TTYPE IS` és a `terminal:` sztring; PETSCII-vonalon semmi |

A másik irányban:

- A közönséges bájtok mindkét irányban érintetlenül mennek át: ANSI, UTF-8, egérjelentések, PETSCII. A duplázott `IAC IAC` (egy 0xFF adatbájt) duplázva marad.
- A hívó ablakméret-jelentései (`SB NAWS`) ennek a boardnak a sessionjét is frissítik (`Session#resize!`), így az átkelés után a board már az új méretben rajzol; friss NAWS-jelentésként pedig akkor adja tovább őket, ha a másik board kérte a NAWS-t.
- A hívó többi telnetparancsát nem adja tovább: ez a board már megegyezett a hívó termináljával, a másik board helyette a gateway válaszait kapja.

Egy PETSCII-vonal (a `session.petscii!` után, lásd [Commodore 64](https://wiki.teletypegames.org/hu/engines/rubbs/commodore)) sem ablakméretet, sem termináltípust nem jelent, így a túloldali kétarcú board (`BBS::Terminal`) Commodore 64-nek veszi a hívót, és PETSCII-ül beszél. Az általa küldött bájtok érintetlenül mennek a vonalra, nem ennek a boardnak a PETSCII-kódolóján át; a gateway csak `translate:`-tel lép közbe (lásd *Egy C64 hív egy ASCII-boardot*).

A `session.line` a hívó nyers socketje. A gateway közvetlenül ezt olvassa és írja; egy saját door, ami változatlanul adja tovább a vonalat, ugyanígy teheti.

## Bontás és hibák

| Mi történik | Mit lát a hívó | Mit csinál a `call` |
|---|---|---|
| a másik board bont | „NAME hung up. Press a key to return.” | legfeljebb 30 s-ig vár egy billentyűre, aztán visszatér: vissza ezen a boardon |
| a kapcsolatot elutasítják, a hoszt ismeretlen, vagy letelik a `connect_timeout` | „NAME does not answer. Try again later. Press a key.” | naplózza a hibát (`gateway: NAME (HOST:PORT): …`), legfeljebb 30 s-ig vár egy billentyűre, `nil`-t ad |
| a cím nem nyilvános, és a `public_only` be van kapcsolva | „NAME may not be called from here. Press a key.” | ugyanez, `BBS::Gateway::Refused`-dal |
| a vonal `idle` másodpercig mindkét irányban néma | semmit | úgy zárja a hívást, mintha a hívó bontott volna |
| a hívó bont | semmit | lezárja a másik board socketjét, és `IOError`-t dob: a hívás véget ér (a `Door#run` továbbdobja) |

Két következmény, amit érdemes tudni:

- Az átkelés akkor ér véget, ha a vonal `idle:` másodpercig mindkét irányban néma (`BBS.config.idle_seconds`, beállítás nélkül 900 s); aki csak olvas, miközben a másik board küld, az bent marad.
- A másik board terminálmódjai úgy maradnak, ahogy hagyta őket. Ha bekapcsolta az egérjelentést, és kikapcsolás nélkül bontott, ennek a boardnak pedig ki van kapcsolva a `BBS.config.mouse`, a hívó terminálja tovább küldi az egérjelentéseket.

## Példa: egy board egy másik doorjaként

A B board a 2473-as porton a fenti TT-80-at futtatja; az A board a 2474-esen gatewayként kínálja, egy nem létező boarddal együtt:

```ruby
# board_b.rb
require 'bbs'
require_relative 'tt80'

BBS.configure do |c|
  c.port = 2473
  c.flow = BBS::Flow.define do
    call do |ctx, runner|
      runner.session.settle(timeout: 1.5)   # előbb jöjjön meg az ablakméret
      BBS::FakeOS::Runner.new(runner.session, TT80, context: ctx).run
      nil
    end
  end
end

BBS.start
```

```ruby
# board_a.rb
require 'bbs'

TT80_BBS = BBS::Gateway.new(host: '127.0.0.1', port: 2473, name: 'TT-80 BBS', public_only: false)
NOWHERE  = BBS::Gateway.new(host: '127.0.0.1', port: 2479, name: 'Nowhere BBS', public_only: false)

BBS.configure do |c|
  c.port = 2474
  c.flow = BBS::Flow.define do
    menu 'Where to?', loop: true do
      option 'TT-80 BBS' do
        call { |ctx, runner| BBS::Door.new(:tt80, TT80_BBS).run(runner.session, ctx); nil }
      end
      option 'Nowhere BBS' do
        call { |ctx, runner| BBS::Door.new(:nowhere, NOWHERE).run(runner.session, ctx); nil }
      end
      option 'Hang up' do
        exit_menu
      end
    end
  end
end

BBS.start
```

`telnet localhost 2474` 120×40-es ablakkal, 1-es opció: a B board kiírja: „120x40 screen”; méretezd át az ablakot, és a SIZE parancsa az új méretet mutatja; a B boardon az EXIT után jön: „TT-80 BBS hung up. Press a key to return.” A 2-es opcióra: „Nowhere BBS does not answer. Try again later. Press a key.”

## Élesben: TTG Gate BBS

A TTG Gate BBS (`bbs/ttg-gate-bbs`, 2399-es port) a hálózatunk minden boardját gatewayen át éri el: a tárcsázómenüje a `config/boards.yml`-ből választ boardot, a `lib/ui/dial.rb` pedig továbbadja a vonalat. A saját boardjaink szomszédok a hoszton, ezért ez a gateway `public_only: false`-t ad meg; a Global Discovery telefonkönyvének boardjait az alapbeállítással, a listázás ellenőrzött nyilvános címén hívja, `translate:`-tel, ha egy C64 ASCII-terminálra készült boardot hív, és minden hívás jelentését rögzíti:

```ruby
BBS::Gateway.new(host: board.host, port: board.port, name: board.name, public_only: false).call(session)
```

Minden board a hoszton publikálja a telnetportját, így a gate konténere a hoszton át éri el őket (`TTG_GATE_BBS_GATEWAY_HOST`, `host.docker.internal`). Linuxos Dockerben alapból nincs `host.docker.internal`; a `compose.yaml` a hoszt gateway-címére képezi:

```yaml
services:
  ttg-gate-bbs:
    extra_hosts:
      - "host.docker.internal:host-gateway"
```

A C64-es hívó a gatewayen át sem jelent ablakméretet, így a túloldali board a PETSCII-arcát választja. (2026-11-01-ig a Teletype BBS-nek saját gateway-doorjai voltak az Alexandriához, a Feediához és a többiekhez; azóta a gate az egyetlen átjáró.)

# BBS::Zmodem

Forrás: `lib/bbs/zmodem.rb`, 0.9.0 óta. Chuck Forsberg ZMODEM-jének küldő oldala, annyi, amennyi kell ahhoz, hogy a hívó terminálja fájlokat mentsen a boardról.

## send\_files

```ruby
BBS::Zmodem.send_files(session, files, wait: 30, telnet: true)
```

| Argumentum | Alapérték | Jelentés |
|---|---|---|
| `session` | kötelező | a hívó sessionje; az átvitel `read_raw_byte`-tal olvassa és `write`-tal írja |
| `files` | kötelező | `[name, data]` vagy `[name, data, mtime]` elemek tömbje; a `name`-ből csak a fájlnév marad, a `data` String (bináris is lehet), az `mtime` Time (alapból `Time.now`) |
| `wait:` | 30 | ennyi másodpercig vár, hogy egy ZMODEM-fogadó válaszoljon |
| `telnet:` | `true` | minden 0xFF-et telnetes IAC-ként dupláz; sima bájtcsőhöz `false` |

| Eredmény | Mikor |
|---|---|
| `:ok` | a terminál átvette a fájlokat, vagy néhányat kihagyott (ZSKIP) |
| `:cancelled` | a hívó Esc-et vagy Ctrl-C-t nyomott, amíg a board a terminálra várt, a terminál öt CAN-t küldött, vagy ZCAN-nal, ZABORT-tal, ZFERR-rel válaszolt |
| `:timeout` | a `wait:` alatt nem válaszolt fogadó, vagy egy fájlt ötször felkínált válasz nélkül |
| `:hangup` | leszakadt a vonal |

`:cancelled` és `:timeout` esetén a board elküldi a megszakító sorozatot (nyolc CAN, nyolc backspace), így a félúton járó terminál is leáll.

## Application#with\_line

Az átvitelhez nyers vonal és tiszta képernyő kell. A `with_line` törölt képernyőn, egérjelentés nélkül adja a sessiont a blokkjának, utána visszakapcsolja az egeret (ha `BBS.config.mouse`), kikényszeríti az application teljes újrarajzolását, és azt adja vissza, amit a blokk:

```ruby
require 'bbs'

README = "Welcome to the files area.\r\n"

FILES = BBS::Application.define do
  helpers do
    def download_readme
      result = with_line do |session|
        session.write("  Sending README.TXT by ZMODEM; Esc gives up.\r\n")
        BBS::Zmodem.send_files(session, [['README.TXT', README, Time.now]], wait: 20)
      end
      message = {
        ok:        'README.TXT is on your disk.',
        cancelled: 'The download was cancelled.',
        timeout:   'Your terminal did not start a ZMODEM download.'
      }.fetch(result, 'The line dropped.')
      BBS::Dialogs.message(self, message, title: ' Download ')
    end
  end

  status_hint('F2', 'Download') { download_readme }
  status_hint('Alt-X', 'Bye') { :halt }
end

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    call do |ctx, runner|
      FILES.run(session: runner.session, session_id: ctx[:session_id], context: ctx)
      nil
    end
  end
end

BBS.start
```

Applicationön kívül közvetlenül a sessionnel hívd a `send_files`-t, például egy flow-ból:

```ruby
require 'bbs'

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    call do |_ctx, runner|
      session = runner.session
      data    = (0..255).map(&:chr).join.b * 64   # 16 KB, benne minden bájtérték
      session.write("Sending BYTES.BIN by ZMODEM; Esc gives up.\r\n")
      result = BBS::Zmodem.send_files(session, [['BYTES.BIN', data, Time.at(0)], ['HELLO.TXT', "Hello\r\n"]])
      session.write("\r\nResult: #{result}\r\n")
      nil
    end
  end
end

BBS.start
```

Mindkét példát az lrzsz `rz`-jével ellenőriztük egy telnethíd mögött: a fájlok bájtra pontosan megérkeztek, a 0xFF-ek is, az eredmény `:ok` volt.

## Mely terminálokkal működik

A SyncTERM, a NetRunner és a Qodem magától elindítja a letöltést, amikor meglátja a ZMODEM-kérést. Sima telnet mögött az lrzsz `rz`-je fogadja. Webes terminálban a zmodem.js (a bbsweb böngészős letöltésként menti a fájlt). A board először `rz`-t és egy CR-t küld, ami egy shellszerű kliensben elindítja az `rz`-t, aztán a ZRQINIT fejlécet, legfeljebb 10 másodpercenként ismételve, amíg a fogadó nem válaszol.

## Hogyan küld

- Minden vezérlőkaraktert (0x00–0x1F és 0x80–0x9F) ZDLE-vel escape-elve küld, így a folyamban nincs csupasz CR, és nem kell telnet BINARY mód.
- Az adat CRC-32-vel megy, ha a fogadó felajánlja (CANFC32 a ZRINIT-jében), különben CRC-16-tal, 1 KB-os alcsomagokban.
- Ha a fogadó pufferméretet jelent, a board ennyi bájt után megáll egy ZACK-ra.
- A ZRPOS attól a pozíciótól indítja újra az adatot; a ZCRC-kérésre a fájl CRC-32-jével válaszol.
- Az utolsó fájl után: ZFIN, aztán „OO”.
- Az egész fájl egy Ruby String a memóriában: tegyél elé méretkorlátot. A WarpEngine BBS legfeljebb `WARP_ENGINE_BBS_DOWNLOAD_MAX` méretű (alapból 1 MB) fájlt küld.

## Buktatók

- **A 0xFF duplázott telnetes IAC, nem ZRUB1.** A ZMODEM a 0xFF-et ZDLE plusz ZRUB1 formában is escape-elhetné, de a zmodem.js nem ismeri ezt: a ZDLE-dekódolása egyszerűen „mínusz 64”. Ezért a 0xFF nyersen megy, telnetvonalon `IAC IAC`-ként duplázva, amit minden fogadó telnetrétege visszaalakít egyetlen 0xFF-re. Ha nincs telnet a két fél között (egy socket egyenesen az `rz`-be, egy cső), add meg: `telnet: false`.
- **Az rz ismétli magát.** Az lrzsz `rz`-je induláskor több ZRINIT-et küld, és ismétli a ZRPOS-t; a küldő figyelmen kívül hagyja a ZFILE-ra érkező ZRINIT-et és azt a ZRPOS-t, ami arra a pozícióra mutat, ahonnan épp elindult, különben kétszer küldené az adatot.
- **PETSCII-vonalon nem.** C64-es vonalon a `session.write` a PETSCII-kódolón megy át, és összetörné a bináris folyamot; a C64 XMODEM-mel kapja meg a fájlját (lásd lejjebb).
- **A `with_line`-on belül.** Applicationben mindig a `with_line`-on belül futtasd az átvitelt: különben az eseményciklus egérjelentései és képernyő-diffelése belezavar a bináris folyamba, és utána nem rajzolódik újra a képernyő.

Élesben a WarpEngine BBS (`bbs/warp-engine-bbs`, `lib/ui/modern.rb` és `lib/ui/transfer.rb`) pontosan a `with_line`-os példát csinálja: lehozza a fájlt a katalógusból, elküldi `send_files(session, [[title.file, data]])`-val (vagy XMODEM-mel), és az eredményből dialógust csinál.

# BBS::Xmodem

Forrás: `lib/bbs/xmodem.rb`, 0.26.0 óta. Az XMODEM, az XMODEM-CRC és az XMODEM-1K küldő oldala: ezt az átvitelt ismerik a C64 terminálprogramjai (CCGMS, Novaterm, UltimateTerm), és a SyncTERM, a Qodem vagy az `rx` is. Az XMODEM nem küld fájlnevet, ezért szólj a hívónak, hogy nevezze el a fájlt, és indítsa el a letöltést a termináljában:

```ruby
result = app.with_line do |session|
  session.write("  Start an XMODEM download of ARIADNE.PRG now; Esc gives up.\r\n")
  BBS::Xmodem.send_file(session, bytes, one_k: true)
end
# => :ok, :cancelled, :timeout (no receiver started) or :hangup
```

- Az ellenőrzést a fogadó választja: egy `C` CRC-16-ot kap, egy NAK ellenőrzőösszeget. `one_k: true`-val a blokkok 1024 bájtosak, ha a fogadó CRC-t kért, különben mindig 128-asak.
- Az utolsó blokkot SUB (0x1a) bájtokkal tölti ki, így a mentett fájl legfeljebb egy blokknyival hosszabb lehet; egy C64-es program ugyanúgy betöltődik.
- PETSCII-vonalon is működik: a bájtok a `session.write_raw`-val mennek ki, a 0xFF duplázott telnet-IAC-ként, és a vonalat az átvitel idejére telnet BINARY módba kéri, utána vissza (sima csőhöz `telnet: false`).
- `wait:` másodpercig (60) vár a fogadóra; közben az Esc vagy a Ctrl-C (RUN/STOP) feladja, és a terminál két CAN-ja bármikor megszakítja. Egy blokk NAK-ra vagy 20 s csend után újra megy, legfeljebb tízszer.

# Lásd még

- [rubbs](https://wiki.teletypegames.org/hu/engines/rubbs) — az áttekintés és a tartalomjegyzék
- [AI-karakterek](https://wiki.teletypegames.org/hu/engines/rubbs/ai) — AI-karakter egy FakeOS `on_unknown`-jában vagy egy window-ban
- [Application](https://wiki.teletypegames.org/hu/engines/rubbs/application) — az asztal, amit a doorok elhagynak, és ahová visszatérnek
- [Flow](https://wiki.teletypegames.org/hu/engines/rubbs/flow) — flow-k doorként, a `call` és az `os` lépés
- [Commodore 64](https://wiki.teletypegames.org/hu/engines/rubbs/commodore) — PETSCII-vonalak, és hogy a gateway miért nem jelent rajtuk ablakméretet
- [Telnet és a store](https://wiki.teletypegames.org/hu/engines/rubbs/telnet-and-store) — a session: `readline`, `readkey`, `read_raw_byte`, `line`
- [Változásnapló](https://wiki.teletypegames.org/hu/engines/rubbs/changelog) — Door (0.6.0), flow-doorok és `colors base:` (0.7.0), ZMODEM (0.9.0), Gateway (0.17.0), XMODEM (0.26.0), a gateway jelentése (0.31.0), `translate:` (0.34.0), `public_only:` (1.0.0)
- [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network) — `BBS::Address`, `BBS.log` és a többi közös építőelem

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [RuBBS](https://wiki.teletypegames.org/hu/engines/rubbs.md)
- Previous: [Adat és hálózat](https://wiki.teletypegames.org/hu/engines/rubbs/data-and-network.md)
- Next: [Rajzolás és témák](https://wiki.teletypegames.org/hu/engines/rubbs/drawing.md)
