Skip to content

FTN Transport

Immortal Barons can carry its league packets over an FTN mail system by itself. The game reads and writes its own private packet directories; the transport wraps those packets for FTN, chooses the next hop, and unwraps them after a mailer session. Neither side has to pretend that a BBS inbound or a BinkP outbox is an ordinary game directory.

The transport has no program of its own. It runs inside the game's inter-BBS commands, the way the original's BRE PLANETARY writes its own netmail, and it is configured with lines in bbs.cfg. A board whose packets travel some other way sets none of those lines and the transport never runs.

This guide covers the operational details. For league concepts, roster HOST lines, and packet authentication, begin with Inter-BBS Leagues. The exact ZIP manifest and game-packet fields are in the developer packet-format reference.

The layers and their directories

There are three independent formats:

  1. A game packet is one signed JSON .brp document. Its contents name its author, final destination, sequence, league, and operations.
  2. A transport bundle is a ZIP file containing one or more unchanged game packets and transport-only routing metadata. Its temporary FTN name has the form NNNNCCCC.BRP.
  3. A stored-message envelope is an optional .msg file telling a tosser to send one transport bundle as a file attach. Obox and direct BSO links do not create this envelope.

The same .BRP extension on the first two is intentional, but every file the transport publishes onto FTN is a ZIP transport bundle unless the link is raw. Its 8.3 physical name is only an alias. Packet ZIP members use the canonical IB filename derived from their contents, and the unwrap step derives that name again rather than trusting any external filename. A raw JSON packet arriving in the mailer's inbound is recognized too.

Keep each owner in its own directory. Every setting below is a bbs.cfg line:

Directory Owner Healthy contents
GameInbound Immortal Barons Unwrapped JSON packets waiting for the planetary step
GameOutbound Immortal Barons Complete JSON packets waiting for the handoff
data/ftn-spool the transport Usually empty; journals appear while a handoff is incomplete
AttachDir (default data/att) connector/tosser NNNNCCCC.BRP bundles waiting to be sent
OutgoingNetmailDir scanner/tosser Outgoing game-owned .msg envelopes
IncomingFileDir mailer Newly received bundles waiting for the unwrap step
IncomingNetmailDir tosser Received .msg envelopes naming an attached bundle
an obox connector/mailer Bundles queued for the peer owning that outbox
a BSO directory tosser/mailer/connector .?lo, .?ut, .bsy, and point subdirectories

Do not point GameInbound or GameOutbound at a BBS inbound, filebox, obox, or BSO directory. The separation is what keeps the game, the transport, and the mailer from reading or deleting the same file at once.

When it runs

Command Before the planetary step After the world is saved
immortal-barons -maint (league board) unwrap handoff
immortal-barons -planetary unwrap handoff
immortal-barons -full unwrap play, write the outbox, handoff
a door session with no -full nothing nothing

The unwrap step runs when IncomingFileDir is set. The handoff runs when any Link line is set, or when OutgoingNetmailDir is set and Mailer is not None. Both take the transport's own lock (barons-ftn.lock in the data directory), and neither holds the game's world lock while it waits for that one.

  • A failed unwrap is reported as a warning. The run goes on and applies whatever is already in GameInbound.
  • A failed handoff happens after the save, so the run's work is kept, and the packets stay in GameOutbound for the next run. The first run to meet a failure ends non-zero and runs the OnFault command, which is what a scheduler's alarm is for. The same failure on later runs is still printed but does not raise the alarm again, as with the league faults; a different failure does, and so does the same one after a run that succeeded.
  • Under -full a caller is waiting, so neither half waits for the transport lock: if another run holds it, that run is doing the same work, and this one says so on stderr and goes on. A failed handoff runs OnFault but does not end the door non-zero.

-planetary can run as often as you like. Run it from the mailer's post-session event, or after the receive command returns, so what arrived is applied at once; keep -maint on its timer for the game day. There is no FTN-wide inbound semaphore, so the unwrap step cannot prove that an unrelated mailer has finished writing a file. It validates a complete ZIP and every member digest before it publishes anything.

The complete exchange order is:

  1. Let the mailer finish its inbound session.
  2. Run immortal-barons -maint or -planetary. It unwraps what arrived, applies it, writes replies, scores and broadcasts, and hands them off.
  3. Run the tosser when using .msg attach links, then let the mailer send its obox or BSO queues.

-data defaults to ./data, relative to the process's working directory. It can be omitted only when the scheduler starts the command in the Immortal Barons installation directory.

Scheduling it safely

A scheduled run wants two things around it. This shape comes from a Synchronet board running the transport in a league:

#!/bin/bash
set -euo pipefail

# Keep the output. Stall reports are written for a run nobody watched.
exec >>/sbbs/xtrn/imb/data/planetary-run.log 2>&1
echo "=== $(date --iso-8601=seconds) ==="

# One run at a time. A second invocation while this one is working exits
# rather than queueing, since there is no FTN-wide inbound semaphore.
exec 9>/sbbs/xtrn/imb/data/planetary-run.lock
flock -n 9 || { echo "skipped: an earlier run still holds the lock"; exit 0; }

cd /sbbs/xtrn/imb
./immortal-barons -maint
/sbbs/exec/sbbsecho /sbbs/ctrl/sbbsecho.ini
/sbbs/exec/jsexec -c/sbbs/ctrl /sbbs/exec/binkit.js

flock -n keeps an overlapping run from starting. A scheduler that fires hourly will one day fire while a slow run is still going, and the lock turns that collision into a clean exit.

The log is opened before the lock so that a skipped run is written down. A run that hangs keeps the lock, and every later run is then skipped. Without the "skipped" line, the log just stops, and the game day, the league's mail and Travel Times all stop with it.

The log matters more than it looks. The handoff names the peers a snapshot is still waiting on and how long they have been behind, and a scheduled run has nowhere to print that unless the output is kept. It is also the only record of every run: the game's own planetary.log gets a line only when a run has something to report.

Keep it in a file of its own. The game writes planetary.log itself and trims it to its last 500 lines, so the script's output there would push the game's fault history out within hours.

cd into the installation directory. -data can then be left off, since it defaults to ./data.

set -e stops the script when -maint exits non-zero, so the tosser and mailer do not run after a new handoff failure or a new league fault. That is usually right, but decide it on purpose. Recoverable transport problems are reported as warnings and do not change the exit status.

Transport settings in bbs.cfg

These lines sit in bbs.cfg beside the board's other settings. The bbs.cfg Reference lists each one's values and default. IncomingFileDir, OutgoingNetmailDir and Mailer are the original's BBS.CFG lines 4, 5 and 7, named after the labels its manual gives them.

Inbound settings

IncomingFileDir /var/spool/binkp/inbound
OboxMeshFanout  Yes
  • IncomingFileDir is the directory the mailer delivers received files into: attachments and raw obox/BSO bundles. Only a directory named here is unwrapped, apart from GameInbound, which always is. Give it once per directory the mailer delivers into. Several mailers use more than one: a session that authenticates with a password and one that does not are filed apart, and the directory you leave out is read by nothing. ENiGMA½ is the clear case — secInbound for an authenticated session, inbound for the rest:
IncomingFileDir /enigma/mail/ftn_secin
IncomingFileDir /enigma/mail/ftn_in

Mystic files an unauthenticated session into an unsecure child of its inbound. That child needs its own line too: the unwrap step reads each named directory and does not descend into it. -ftn-status names any packet left unread in a directory it can see, including such a child, so the report tells you a line is missing. - IncomingNetmailDir is where the tosser leaves received .msg envelopes. Left unset it means every IncomingFileDir, so the order of those lines cannot decide whether an envelope is seen. Set it only to look somewhere else entirely. - OboxMeshFanout defaults to Yes. It controls only an unaddressed broadcast received without an attach envelope. See Mesh warning.

Stored-message attach settings

OutgoingNetmailDir  /sbbs/fido/netmail
AttachDir   /sbbs/fido/ib-attach
Mailer      Binkley
SubjectPath Absolute
  • OutgoingNetmailDir is where the game writes outgoing .msg envelopes for the scanner to pack. It is required when any peer uses Attach, including the default for a peer with no Link line.
  • AttachDir holds outgoing bundles for Attach and BSO links. If omitted, the transport uses data/att (#231: deliberately not nested under its other spool directories, since this is the one path a mailer's Subject field has to spell out under a hard byte limit — see Keeping attach subjects short).
  • Mailer says how the netmail asks for the attachment to be deleted once sent, or turns netmail off.
  • SubjectPath says how the attachment path is spelled in the Subject. The stored-message Subject has 71 usable bytes, or 70 with the ^ prefix.

On the receiving side, the tosser normally leaves both the bundle and its stored-message envelope in the configured inbound. The unwrap step verifies that the message:

  • has the file-attach attribute
  • identifies Immortal Barons
  • names exactly one attachment inside an IncomingFileDir
  • comes from a roster address
  • is addressed to this board

Only after all packets are delivered or forwarded does it delete both files. Other netmail is never removed.

Keeping attach subjects short

The IB installation and data directories may be arbitrarily deep if AttachDir is a short absolute path. For example:

AttachDir /var/spool/ib-attach
SubjectPath Absolute

This writes a Subject such as /var/spool/ib-attach/7PRK0001.BRP; the installation path is not included. A relative AttachDir is resolved beneath the data directory and therefore does not provide this workaround. The data directory is always spelled in full, even when -data is relative, so the default data/att puts the whole installation path into the Subject. Create the attachment directory with ownership and permissions that allow both the game and the mailer to use it.

Do not use /tmp or another automatically cleaned directory for this purpose. An attachment may remain queued across a reboot or cleanup interval, and removing it leaves the mailer with a dangling .msg envelope. Use a short, persistent spool directory instead.

SubjectPath Basename shortens the Subject further, to only 7PRK0001.BRP, but it is correct only when the mailer is independently configured to search the same directory named by AttachDir. The game cannot infer or configure that mailer search path, and not every mailer has one to configure — SBBSecho does not: it takes the directory straight from the Subject with no attachment search path at all, so a bare filename is looked for in its ctrl directory and reported not found. Basename is not a usable option on SBBSecho for this reason; use AttachDir (or an absolute SubjectPath prefix naming the same directory) instead. BSO flow files and oboxes do not use a Type-2 Subject, so the 71-byte limit applies only to Attach links.

Each Link identifies a directly connected IBBS roster node:

Link 2 Attach
Link 3 Obox /var/spool/binkp/outbox/node3
Link 4 BSO /var/spool/ftn/outbound.309 Hold

The modes are:

  • Attach creates a game-owned .msg in OutgoingNetmailDir addressed to that next hop. It takes no per-link directory. It is the compatibility default, and it is the weakest of the three: a tosser stands between the game and the mailer, and the Subject limit above is its alone. Prefer BSO where the mailer keeps a Binkley-style outbound.
  • Obox atomically publishes the bundle in the named peer-specific directory. The mailer must treat a final-name appearance as a complete file.
  • BSO takes the destination's .bsy, then either merges into a compatible Barons bundle already named by that flavor's flow file or publishes a new bundle in AttachDir and adds a delete-after-send flow entry. The directory must be the exact root for that address's zone; the transport does not guess domain-to-zone mappings.

A link mode has to be one the peer can receive. It describes a handoff between two boards, so the mode you send with is only half of it:

  • Attach requires the receiving board's mail system to leave the .msg envelope as a file where its unwrap step can read it — its IncomingNetmailDir, which unset means every IncomingFileDir. Synchronet with SBBSecho does that. Mystic does not: it tosses netmail into its own message bases and leaves no .msg file behind, so a Mystic board can never claim an attach. Reach a Mystic peer with Obox or BSO.
  • Obox and BSO need nothing of the receiver but its mailer, since the bundle arrives as an ordinary file in the inbound.

A board sent an attach it cannot claim does not refuse it. The bundle stays in its inbound and is skipped on every run, with no warning on either side, while the sender's own logs report a clean handoff. Since a peer with no Link line of its own uses Attach, a bbs.cfg carrying no links at all cannot reach a Mystic board — which is how this was found on a three-board test rig, after 30 bundles had collected.

A BSO link takes an optional flavor. A point address uses the standard <net><node>.pnt/<point>.?lo layout automatically.

If a destination has no Link, it uses Attach, so a board with no Link lines sends every next hop through the .msg chain. This fallback applies to addressed routing. Once any Link is present, unaddressed mesh fanout uses only the explicitly listed peers; otherwise the transport would invent graph edges and defeat a ring or partial mesh. List every direct fanout neighbor, including one that uses Attach. A hub may freely mix all three modes.

Plain packets for boards that cannot read a bundle

A board runs raw by default, and that is deliberate for this release. A peer that cannot unwrap a bundle does not merely fail to read it — its inbound run stops on the first one it meets and applies nothing at all until someone removes the file by hand. So a sysop who upgrades and configures nothing keeps sending what every board already understands, and has to ask for the faster shape rather than arrive at it.

The test is what release the peer runs, not how it is set up. From v0.2.0, -planetary, -maint and -full unwrap a bundle wherever it lands: in an IncomingFileDir, or straight in GameInbound. So a board whose mailer drops files into GameInbound — the file-drop arrangement described under Optional FTN handoff — reads a bundle with no FTN settings at all. A board on an older release cannot read one, whatever it has configured.

A bundle found in GameInbound is checked as one from IncomingFileDir is, and its packets are written beside it for the planetary step. A packet in it addressed to another board is passed on by the planetary step, as it would be had it arrived unbundled; nothing is forwarded over FTN from there. A file that starts like a bundle but cannot be read is set aside in ftn-spool/bad once it is five minutes old.

Turn bundling on for the whole board with Bundled once every peer runs v0.2.0 or later:

Bundled Yes

Or per peer, when a league is part way through upgrading. Add Raw or Bundled to the end of any link line; it overrides the board-wide setting for that peer only:

Bundled Yes
Link 3 Obox /home/bbs/filebox/peer Raw

Raw is a modifier rather than a mode of its own, because the envelope is what an old board cannot parse, not the way the file travels: it composes with attach, obox and BSO alike, and on a BSO link it is never merged into a bundle already advertised in the flow file.

Raw costs one attachment alias per packet, gives up coalescing, and carries no routing manifest — a receiver rebuilds the route from the packet's own origin and learns nothing about which peers a broadcast already reached, bounded by the hop limit and by replay detection instead. That is exactly how the transport behaved before bundles existed, and it is the price of reaching a board that cannot read one.

On a routed league only the Coordinator has to do anything. Every member sends it plain packets already, and its unwrap step reads those whatever they are. It is the Coordinator's own sends that need Raw, and it can drop the setting for one member at a time as each upgrades, or switch the whole board to Bundled Yes once the last one is done.

Configuration examples

Two boards using direct oboxes

On node 1:

IncomingFileDir /mystic/echomail/in
Link 2 Obox /mystic/filebox/ib_z99n1n2

On node 2, reverse the peer and directory:

IncomingFileDir /mystic/echomail/in
Link 1 Obox /mystic/filebox/ib_z99n1n1

The game itself uses private paths:

GameInbound  inbound
GameOutbound outbound

The roster makes node 1 the hub:

1 HOST 2 3 4
Hub BBS
777:10/1
...

The hub can use a different local handoff for every child:

IncomingFileDir /srv/ftn/inbound
OutgoingNetmailDir /sbbs/fido/netmail
AttachDir /srv/ib/attach
Mailer Binkley

Link 2 Attach
Link 3 Obox /srv/binkd/obox/node3
Link 4 BSO /srv/binkd/outbound Normal

A packet from node 2 to node 4 arrives inside node 2's attach. The hub's unwrap step unwraps it, leaves its signed JSON bytes untouched, and publishes a new transport bundle through node 4's BSO flow. It does not wait for the hub's next planetary run.

Synchronet

BinkIT keeps a Binkley-style outbound, so a Synchronet board should hand the bundle to that outbound directly:

IncomingFileDir /sbbs/fido/inbound
Link 1 BSO /sbbs/fido/outbound Normal

The outbound chain is:

immortal-barons -maint -> NNNNCCCC.BRP + .flo entry -> BinkIT

SBBSecho is not in that path. The Subject byte limit does not apply either, because a flow file holds the whole pathname, so AttachDir can stay at its default.

Name the outbound directory for the destination's zone. Synchronet writes the system's own zone into the base directory and every other zone into outbound.<zone in hex>. See Direct BSO/FLO handoff for what the transport writes there.

Direct BSO/FLO handoff

IncomingFileDir /var/spool/binkd/in
AttachDir /var/spool/ib/attach
Link 3 BSO /var/spool/binkd/outbound Normal

For peer 1:229/300, the transport uses 00e5012c.bsy and 00e5012c.flo in the configured BSO directory. The flow line begins with ^ and contains the full attachment pathname, asking the mailer to delete it after success.

While it owns 00e5012c.bsy, a later run may add its new snapshot to an existing compatible Barons bundle referenced by that flow file. It atomically replaces the ZIP wrapper at the same pathname; every signed packet member is copied byte-for-byte. Exact-member deduplication makes a replay after a crash idempotent. Entries without ^, files outside AttachDir, non-Barons files, other leagues or transmitters, and full bundles are not modified.

For point 1:229/300.4, the paths are:

00e5012c.pnt/00000004.bsy
00e5012c.pnt/00000004.flo

If .bsy already exists, that peer is normally reported busy and its durable spool transaction remains pending. The invocation does not poll or sleep: other peers continue, and the next scheduled run resumes pending transactions before claiming new work. The narrowly scoped exception is an old semaphore carrying the transport's own PID marker; recovery is described below.

Bundling, names, and recovery

Every handoff takes one fixed snapshot under the same game.lock used by Immortal Barons. Packets written after that claim wait for the next run. All packets in the snapshot which share a next hop go into one ZIP bundle.

The FTN alias is NNNNCCCC.BRP:

  • NNNN is a four-character base-36 namespace derived from this league and the transmitting hop's node number. The encoding reserves a distinct namespace for legacy league-0 packets so one such packet cannot wedge a transport batch; that compatibility namespace is not a substitute for the Coordinator-assigned league number.
  • CCCC is a persistent four-character base-36 counter.
  • The counter advances for every physical handoff, including each broadcast copy, and does not reset with a new game season.

The counter is reserved before publication, so a crash may skip a value but cannot reuse it. Wrap is reported loudly. An existing alias is never overwritten; the allocator advances until it finds a free one.

Attach and obox bundles are immutable after publication. Obox reuse may be possible with a particular mailer's documented claim/rename protocol, but no common obox lock is assumed here; it remains disabled pending interoperability and race testing.

BSO is the exception. FTS-5005 gives the destination one .bsy covering its outbound files. After acquiring that semaphore, the transport may safely rebuild a compatible bundle already advertised in the selected flow file. It releases .bsy only after the replacement is durable.

FTS-5005 permits a .bsy to contain one line of PID information. A semaphore created here contains barons-ftn pid=<number>, and the process also locks its first-byte range on Windows, or takes a whole-file flock on Unix, for the complete BSO update. A later run removes that semaphore as stale only when all three checks agree:

  • the marker is exactly ours
  • the ownership lock can be acquired non-blockingly
  • the file is at least five minutes old

It then retries the standard exclusive .bsy creation. Thus a live run remains protected even if its semaphore's timestamp is old, while a crash becomes recoverable without guessing from age alone.

An empty, malformed, young, locked, or foreign-marked .bsy is simply busy. The transport never applies its five-minute policy to a mailer or tosser's semaphore; the mailer's own FTS-5005 age/restart mechanism remains responsible for those. A legacy empty semaphore left by an older release is likewise indistinguishable and follows the mailer's policy. Never manually clear .bsy files merely because a peer is slow or offline.

Progress journals live under data/ftn-spool; the claimed bundle itself is written to AttachDir (default data/att) or to the peer's obox. A target is marked complete only after its bundle and .msg, obox placement, or BSO flow entry are durable. A restart uses the same alias and bytes, recognizes an already-created attach message, and completes only unfinished targets.

Inbound rejection is per packet member, not per bundle. A wrong-league packet, unknown destination, or routing cycle is recorded in the receipt while valid members are still delivered or forwarded. After those valid members finish, the complete original transport wrapper moves to ftn-spool/bad so the rejected routing context remains available for diagnosis; it is not retried on every later run. A local canonical-name collision is different: the receipt and source stay pending because the operator must decide which bytes are valid.

Both halves of the transport hold barons-ftn.lock. Movement between the connector spool and the private game directories also holds game.lock. The lock order is always transport first, game second, and the game never waits for the transport lock while it holds its own. Atomic renames and exclusive file creation remain additional protections.

On local delivery, an existing canonical filename with identical bytes is logged as a duplicate, not rewritten, and not counted as a new delivery. If that canonical name already belongs to different bytes, the transport reports a collision and retains its receipt and source rather than overwriting either file or inventing a noncanonical name.

Routing and broadcasts

The JSON packet names its final destination. A transport bundle is addressed only to the next FTN hop. At a hub, the unwrap step reads enough JSON to choose the next hop but copies the original JSON bytes into the new bundle without changing or re-signing them. The actual node route and broadcast coverage live in the ZIP manifest and are discarded before local game delivery. The final route node is the transmitting hop, and the route length supplies the hop count, so neither fact is stored twice.

A routed league with HOST lines is the normal and recommended arrangement. The game creates addressed broadcast copies before signing, and the connector routes each copy normally.

Mesh warning

An old-style unaddressed broadcast has no final node. When it arrives over Obox or BSO and OboxMeshFanout Yes, the unwrap step delivers it locally and sends it to every configured peer in neither its route nor its covered list. Before a sender publishes sibling copies, it puts every durably scheduled recipient in the common covered list. This prevents those recipients from reflexively cross-sending the same broadcast.

These lists limit amplification; they do not make distributed fanout exactly once. In particular, a simple cycle of four or more nodes can have independently scheduled branches meet after both are already in flight, producing duplicates. The hop limit prevents an infinite loop, and canonical-name plus exact-byte duplicate detection lets the inbound converge safely.

If the topology is a true mesh and the source already reaches every board, set:

OboxMeshFanout No

Then the unwrap step delivers an unaddressed broadcast locally and stops. The source transport is responsible for putting one copy on every required direct link. Do not use this switch to disguise a physical star whose roster claims to be a mesh; describe that star with HOST lines instead.

Troubleshooting by file location

This table is about the transport. When the question is the game's — a packet that arrived and was refused, held, or quarantined, or a board that has gone quiet — see Inter-BBS Troubleshooting.

Where files accumulate Meaning Action
GameOutbound The handoff did not run, or failed Run immortal-barons -planetary; read its error
ftn-spool/out At least one target is busy or failed Read the warning; inspect that peer's .bsy, path, or netmail directory
AttachDir (default data/att), envelope still in OutgoingNetmailDir Normal. The attachment waits for the tosser to pack the .msg that names it Nothing. Run/check the tosser
AttachDir (default data/att) with no .msg/flow Attach or BSO queue publication failed Check subject length, OutgoingNetmailDir, BSO directory, and permissions
OutgoingNetmailDir .msg The tosser has not packed outgoing netmail Run/check the tosser and allow file attaches
BSO .?lo The mailer has not successfully sent the referenced bundle Check peer address, password, route, and .bsy
peer obox The mailer has not sent or acknowledged the file Check the peer session and outbox mapping
IncomingFileDir The unwrap step did not run, ran before receive completion, or rejected the wrapper Run -planetary after the session and read warnings
IncomingFileDir, listed by -ftn-status as unclaimed Attach bundles whose .msg envelope never arrives here — see Per-peer links unzip -p FILE manifest.json to confirm "delivery": "attach"; have the sender switch that link to Obox or BSO
a subdirectory of IncomingFileDir, named by -ftn-status The mailer filed an unauthenticated session's files apart from the rest; the unwrap step reads each IncomingFileDir and nothing below it Give that subdirectory its own IncomingFileDir line, or fix the session password for that peer and move the waiting files up
ftn-spool/in Local publication or transit handoff is incomplete Correct the named target; the next run resumes it
GameInbound The planetary step has not applied the unwrapped packets Run immortal-barons -planetary
GameInbound, a bundle The unwrap did not run, another run held the transport lock, or the bundle's sender is not on this board's roster yet Run immortal-barons -planetary and read its warnings
ftn-spool/bad An outbound packet was malformed/unroutable, an inbound bundle contained a rejected member, or a file in GameInbound started like a bundle and could not be read Preserve it for diagnosis; correct the producing board, route, league, or roster

One bad packet or busy peer does not stop unrelated destinations. Do not delete spool journals to make a warning disappear: they are the record that prevents partial work from being forgotten or blindly repeated.

The same goes for the files in AttachDir, which look more disposable than they are. On a hub most of them are not this board's own mail at all: they are packets that arrived addressed to somebody else and were re-published toward their next hop. The board that sent one has already seen it handed over successfully, so it will never send it again — deleting the attachment loses that mail for good, and the recipient's only symptom is a board that went quiet.

Before removing anything from AttachDir, decide which of the two rows above applies, because they look identical in a file listing:

  • Run immortal-barons -ftn-status. A receipt held in transit for another board names that board and that peer's last error. Anything it still lists is owed to somebody.
  • Find the envelope. Each Queued <packet> for <next hop> as <message> line from the run that created the attachment names the .msg that carries it, so the mapping is in the log. An envelope still sitting in OutgoingNetmailDir means the tosser has not packed it yet and the pair is fine. An envelope that has gone while its attachment stayed is the fault worth chasing.

What a healthy spool looks like

A file count is the wrong measure here, and reading one as a backlog is the mistake to avoid. ftn-spool/out holds one child per claimed snapshot of the game's outbound, and a snapshot is kept whole until every target in it has been published — so one unreachable peer retains that snapshot's packets and the bundles of the peers that already went out. A peer that stays unavailable therefore produces a growing series of snapshot directories while healthy peers keep flowing, which is working as intended and not a queue of that many unsent packets.

What to read instead:

  • The handoff says so on every run. A run that publishes nothing because its peers are busy prints how many snapshots remain and which peers they wait on, so a scheduled event's log distinguishes an empty system from a stalled one. The same peer named run after run is the signal worth acting on.
  • ftn-spool/in should drain. A receipt kept across runs means a canonical-name collision whose bytes differ, a transit handoff that has not completed, or a source or envelope that could not be removed. Its receipt.json names which.
  • The journals date themselves. A snapshot records when it was claimed and when a target in it last published, and a target that failed keeps the reason. A run that queues nothing therefore reports how long the oldest snapshot has gone without progress and why each peer is behind, rather than only how many are waiting. All three fields are optional: a journal written before they existed still loads, and its file date stands in for the age.
  • immortal-barons -ftn-status answers all of this and changes nothing. It reports each peer's unfinished snapshots longest wait first, with the recorded reason, the pending inbound receipts and which of the three ways each is stuck, any journal that will not parse, and how many packets are set aside. Reach for it before reading directories by hand.
  • It also reports packets nobody has claimed, which are in neither spool. A file the transport never took is a file no journal knows about, so the counts above cannot show it. -ftn-status lists any .BRP that has sat in an IncomingFileDir, or in a subdirectory of it, for over an hour, and the unwrap step warns about the same files as it runs. The troubleshooting guide has the two causes and what to do about each.
  • immortal-barons -league-check reports the same backlog alongside the rest of the league setup, for the sysop who has gone looking there first. A waiting peer is shown but not marked a fault — a peer can be legitimately offline for days — while a journal that cannot be read is a FAIL, because nothing else will ever mention it. A packet nobody has claimed is a FAIL for the same reason: it sits in neither spool, so no other line here counts it.
  • ftn-spool/bad only grows. Nothing is retried from it and nothing removes it; it is yours to read and clear once the producing board, route, league or roster is corrected.
  • AttachDir (default data/att) should also drain: a bundle sitting there with no matching .msg/flow entry means the Attach or BSO queue publication step failed after the bundle was written — check the same causes as the troubleshooting table above (subject length, OutgoingNetmailDir, BSO directory, permissions). This directory is deliberately not under ftn-spool/ and not shown by -ftn-status's spool report — it is the one transport path a mailer's Subject field has to spell out under a byte limit, so it lives where the sysop can point AttachDir at a short location if the default does not fit.

A published alias belongs to the mailer, not to the game. Once a target is published and marked done, its snapshot can disappear and the game no longer holds a journal saying that file is outstanding — the evidence moves to the transport:

Mode What holds the alias Cleared when
Attach the matching .msg names it the tosser and mailer chain sends it
BSO a ^ entry in the flow file the mailer sends it and deletes it
Obox the queued file is the mailer's own state the peer session takes it

So an alias sitting in a directory is not evidence of an abandoned file. A peer offline for a week is still a valid queue, and age alone cannot tell the two apart. Report growth, and delete only with mode-specific proof from the list above that the owning mailer is finished with it.

Upgrading from barons-ftn

Before this release the transport was a separate program, barons-ftn, with its own settings file, ftn.cfg. Both are gone. On a board that still has ftn.cfg, -maint and -planetary refuse to run and print that file's settings rewritten as bbs.cfg lines. Paste those lines into bbs.cfg, delete ftn.cfg, and take barons-ftn out of the scheduler and the mailer's hooks: -maint and -planetary now do its work. bbs.cfg settings spelled the way an older release wrote them are refused the same way, with their replacements.

A door launched with -full never keeps a caller out over this. It prints the same lines, skips the league exchange, and lets the caller play. -ftn-status prints them too, and then its report.

To check the move, run -ftn-status once ftn.cfg is gone. It names each half of the transport that has no lines in bbs.cfg ("Sending: no OutgoingNetmailDir or Link line…"). A board that ran barons-ftn should see neither line.

An old barons-ftn binary left on disk and still scheduled does no harm once ftn.cfg is deleted. It cannot find its settings, so it exits with an error on every run and moves nothing. The transport lock keeps its old name, so an old copy that does still run queues behind the game's own transport instead of running beside it.

The packet format does not change in this release, so boards can upgrade one at a time.

Turning bundles on

A board sent a ZIP bundle needs a release that unwraps one: v0.2.0 or later, which reads a bundle in IncomingFileDir and in GameInbound alike. An older release cannot parse it as a JSON packet. Upgrade every receiver before any sender switches to bundles:

  1. Upgrade every board, and run -planetary after receive sessions.
  2. Verify that raw .brp traffic is still delivered to the game.
  3. Configure the per-peer Link modes.
  4. Turn on Bundled Yes on the senders.

Because the unwrap accepts raw JSON packets, steps 1–2 can be completed without coordinating an exact cutover minute.

This is the rolling case, and a protocol change is not. The order above covers the transport container, which the game's Protocol number does not describe. When a release moves that number, the league closes the game, lets every board finish sending what it has queued, and switches together. Its release notes will say so.

An Attach link with no AttachDir set gets one under the data directory (see Stored-message attach settings). A board whose data directory is already deep, as a Synchronet door is under the usual <sbbs>/xtrn/<door>/data layout, can lose all its Subject margin to that alone and publish nothing. If the handoff fails with an attachment subject ... is N bytes error, set AttachDir to a short, persistent directory outside the data tree per Keeping attach subjects short.

A Synchronet board should use a BSO link instead. See Synchronet.