ANetBBS Changelog

Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.58 (July 2026). Full release: August 1 2026.

v1.0b2.28 — A-Net Game Server bundled by default + Ebook Reader (July 2026)

  • FEATURE: A-Net Online's rlogin game server (450+ door games) now ships pre-installed and active like LORD does. Each install gets its own randomly generated password and BBS tag on first boot — private to that BBS, never in the public source, and never regenerated afterward. Sysops can change either any time at Admin → Door Games → A-Net Game Server.
  • FEATURE: new door_rlogin BBS Tag field — the tag now has its own admin-form field instead of being hyphen-joined into the user-template text, purely for clarity (the wire format itself, username-TAG, is unchanged).
  • FEATURE: new built-in web game, Ebook Reader — search + read free Project Gutenberg classics in a book-styled reading view (serif type, page-turn navigation), with bookmarks, reading history, and download-to-.txt. Also has a terminal version (telnet/SSH/rlogin, main-menu hotkey K) with the same feature set.
  • FEATURE: per-front-end Web/Terminal toggle on every game (Admin → Games) — lets a sysop run the Ebook Reader as terminal-only, web-only, or both.
  • FIX: bundled-door seeding hardcoded max_nodes=1 for every stock door — wrong for a 20-slot remote multiplayer server. Now respects a per-door override.
  • FIX: lrzsz (ZMODEM, used by every terminal file transfer) was missing from install.sh's required packages — fresh installs had no working terminal downloads out of the box. Now installed automatically. Existing installs upgrading via update.sh need sudo apt-get install -y lrzsz once by hand (update.sh doesn't manage system packages).

v1.0b2.27 — Telnet door game servers (July 2026)

  • FEATURE: new door_telnet game type for external telnet-only game servers (e.g. TWGS — Trade Wars Game Server, like game.a-net-online.lol:2002, A-Net Online's own TWGS). No pre-authentication handshake like door_rlogin — just host:port, user logs in interactively on the remote side. Works on both web and terminal doors, mirrors the rlogin door architecture. Includes a small RFC 854 option-negotiation filter so the remote server doesn't hang and negotiation bytes never reach the player's screen. Configure via Admin → Games → Game Type → "Telnet Door Server".

v1.0b2.26 — MRC: terminal fixes + stale-session cleanup (July 2026)

  • FIX: /mentions always showed 0 — mention detection was wired to event types the bridge never sends; moved to the real mrc_message path. Status-bar !N indicator now works live too.
  • FIX: messages up to 140 chars could get silently truncated to ~120 — terminal client now accounts for the bridge's handle-prefix overhead and splits long messages into (1/2)/(2/2) chunks instead of losing the tail.
  • FIX: mention indicator was reverse-video red, illegible on some terminals — switched to explicit fg/bg colors.
  • FIX: /mentions output misaligned on wrap — restructured into a header line + indented body line per mention.
  • CHANGE: /help and /helpserver swapped — /help now asks the hub for its own help, /helpserver shows the client's local command list.
  • FIX: outgoing text color never persisted across reconnects — now restores on join and saves via set_style on change.
  • FIX: Tab nick-completion gave no feedback on zero matches and could dump an unbounded candidate list — capped at 12 with a "+N more" hint. Also fixed a real bug where a visibly-present user couldn't be found: /who's roster is comma-separated on the wire, but the parser was splitting on whitespace. Bridge now also refreshes the roster on /who.
  • FIX (critical): a mid-round version of the Tab-completion fix briefly deadlocked terminal MRC solid on the next Tab press. Fixed, covered by a timeout-guarded test.
  • FIX (bridge): a dropped connection left MRC sessions looking permanently logged in, causing "you can only be logged on once" from the upstream hub. Added a WebSocket heartbeat so the existing (correct) cleanup logic actually runs.

v1.0b2.25 — Paginate large file areas (July 2026)

  • FEATURE: file area listings are now paginated (50 files/page) instead of rendering the entire area on one page — same fix pattern as the CHANGELOG page's own pagination (v1.0b2.22), reported after a 7,000+ file door-games area took a while to load. Sort (name/size/date) applies before slicing so page contents stay correct, and pagination links preserve the active sort. New shared ListPagination helper (anetbbs/web/list_pagination.py) with ellipsis-truncated page links, since a large area can span far more pages than the CHANGELOG ever will.

v1.0b2.24 — Configurable login auto-ban (July 2026)

  • FEATURE: the login auto-ban is now sysop-configurable from Admin → IP Bans instead of a hardcoded permanent-only trigger. Reported by Firehawke (feature request 2026-07-03): 10 failed logins in 5 minutes triggered a permanent IP ban with no way to configure the threshold, duration, or disable it — caught one of their test users off guard. Now configurable: attempt limit, time window, ban duration in hours (0 = permanent, still available), and an enable/disable switch. Default ban duration changed from permanent to 1 hour, matching the proposed resolution; the 10-attempts/5-minute trigger threshold is unchanged by default but now editable. New AutoBanConfig singleton model, settings card added to the existing IP Bans admin page. 8 new tests in tests/test_auto_ban.py.

v1.0b2.23 — Multi-screen welcome/goodbye/newuser sequences (July 2026)

  • FEATURE: sysops can drop in multiple variants of any lifecycle ANSI screen (welcome, goodbye, newuser, or a custom slot) and ANetBBS shows all of them, in order, every login — the classic Synchronet logon1.ans/logon2.ans/logon3.ans multi-screen convention. welcome132.ans is shown first; add welcome132_2.ans, welcome132_3.ans, etc. and they display right after, same login — works for the data/text/<slot>.ans override and the bundled stock screens, all three width modes. Each variant controls its own @PAUSE@; nothing is auto-inserted between screens. A single file (the common case) behaves exactly as before, no persisted state of any kind. Prefer one random pick instead of the whole sequence? Use _ran naming (welcome_ran.ans, welcome_2_ran.ans, ...) — shows just one, chosen at random; wins over plain numbered naming if both exist. See docs/04-ansi-screens.md.
  • FIX (pre-existing, unrelated to the feature above — found while testing on the Pi3): an active BbsAnsiScreen DB row with an empty body blocked the fallback to the bundled stock screen, so the screen silently showed nothing at all instead of falling back. Now an empty-but-active row is treated the same as no row.
  • FIX: welcome132.ans and welcome.ans were the last stock screens with a box border still on them, plus (for the 132-col one) a duplicated Sysop/Version line — rebuilt both borderless to match goodbye132.ans/newuser132.ans's style. Added @PAUSE@ to all three welcome variants and to goodbye.ans — none of them had it, so the banner rendered and immediately fell through to the next prompt, easy to miss at normal typing speed.

v1.0b2.22 — Paginate the web Changelog page (July 2026)

  • FEATURE: /docs/CHANGELOG was rendering this entire, ever-growing file as one markdown pass on every load, getting slower release after release. Now paginated at 15 version-entries per page (newest first), with Prev/Next + page-number navigation. Other /docs/* pages are unaffected. Verified against the real route: page 1 has the newest entry, page 2 differs, out-of-range page numbers clamp instead of erroring.
  • Also fixed the "Current release" line above, which was stuck at v1.0b1.6 for a long time — added a note to the version-bump checklist so it's updated alongside every new entry from now on.

v1.0b2.21 — Fix: pyspellchecker was never actually installed (July 2026)

  • FIX: setup.py's install_requires is a separate, hand-maintained list from requirements.txtupdate.sh/install.sh install via pip install -e ., which reads setup.py. v1.0b2.20 added pyspellchecker to requirements.txt only, so it never actually installed through the normal deploy path on any environment. The feature degraded silently (by design — missing spellchecker just means the feature is unavailable, not a crash) rather than erroring, so it looked like a UI bug ("misspelled words aren't underlined") rather than a missing-dependency bug until traced back. Added pyspellchecker>=0.8.0 to setup.py too.

v1.0b2.20 — Spell check + clickable URLs, web and terminal (July 2026)

  • FEATURE: anetbbs/features/anedit.py (terminal message editor) gains live spell check — misspelled words underline as you type, and /spell (/sp) jumps to the next one with a suggestion box (0-5=replace, N=skip, Esc=cancel). Quoted lines and ALL-CAPS jargon (SSH, QWK, ANSI, ...) are excluded from checking. New dependency: pyspellchecker (pure Python, bundled dictionary, no network, degrades silently if missing — same pattern as the optional markdown/bleach deps).
  • FEATURE: Web compose forms (PM, board post, echomail, netmail) now set spellcheck="true" explicitly on their textareas, enabling the browser's native spellcheck (red squiggly underline, right-click suggestions).
  • FEATURE: https:// URLs are now clickable everywhere a message body is shown. Web: render_msg.py's msgbody/msgbody_rich filters wrap plain URLs in <a href target="_blank"> (board posts still auto-embed image URLs as <img>, that's unchanged — just extended to non-image links too). Terminal: bbs_ui.py wraps URLs in OSC 8 hyperlink escapes when displaying board threads, PM bodies, and InterBBS instant messages — clickable on terminals that support it (Windows Terminal, iTerm2, kitty, gnome-terminal), silently ignored (URL text still shows) on ones that don't (SyncTERM and most classic BBS terminal clients).
  • Verified end-to-end against a real running instance (in-memory-DB Flask test client hitting the actual routes/templates) as well as direct unit tests of every new code path, including an explicit check that the spell-check suggestion picker can't recurse unboundedly on a document full of unrecognised words.

v1.0b2.19 — Fix GitHub #3 for real: the active v2 thread reader was never patched (July 2026)

  • FIX: GitHub issue #3 (terminal message reader truncating long paragraphs instead of word-wrapping them) was marked fixed in v1.0b2.15's changelog, but a sysop retested against v1.0b2.18 and found it still truncating — with an exact root-cause diagnosis and a tested patch attached to the issue. The v1.0b2.15 fix touched read_thread() in anetbbs/features/bbs_ui.py, but that method is shadowed at module import time: near the bottom of the same file, BBSMenuUI.list_threads = _list_threads_v2 and BBSMenuUI.read_thread_v2 = _read_thread_v2 monkey-patch the class, replacing the class-body methods with newer "v2" versions that add inline N=new-thread and R=reply support. The board menu actually calls list_threads_list_threads_v2read_thread_v2_read_thread_v2, which still had the original bug: for line in (p['content'] or '').splitlines(): ... line[:_line_w], silently discarding everything past the terminal width on any paragraph without manual line breaks. Rewrote _read_thread_v2 to build its output through _wrap_text() and the _page_lines() pager (same infrastructure the v1.0b2.15 fix added), so long paragraphs wrap and long threads now paginate with --MORE--, closing the gap the original bug report asked for ("Long messages should be paginated") that even the reporter's own patch didn't fully close. Also fixed _line_w = max(76, _w - 4)max(20, _w - 4): the old floor of 76 could make lines overflow on a narrower-than-80-column terminal.
  • Verified by reconstructing the full paragraph from its wrapped output and diffing word-for-word against the original — confirmed no content loss — before shipping. Also grepped every BBSMenuUI.<name> = <name> override in the file afterward: private messages and Inter-BBS instant messages have no such shadow, so their v1.0b2.15 fixes are confirmed to still be the active code path.

v1.0b2.18 — ANetCRAFT: day/night cycle was 40 seconds, now 10 minutes (July 2026)

  • FIX: DAY_TICK was 500 (40 seconds/day at the 80ms tick rate) — reported by Jerry as far too fast to feel like a real survival mechanic. Changed to 7500 (~10 minutes/day). All consuming code (DayT()/_day_t(), the "Day N" counters, the saved-game lobby preview) derives from this one constant via division/modulo, so the change propagates cleanly with no other edits needed. Applied identically to both this Python version and the standalone C# ANetCRAFT-Door project.

v1.0b2.17 — ANetCRAFT: fix severe lighting-related slowdown (July 2026)

  • FIX: v1.0b2.16's World.light_at() was ported straight from the C# version's per-cell design — an O(R²) 17×17-tile neighborhood scan for every query, called once per visible cell (~1560 cells/frame in _draw_world). Cheap in compiled/JIT'd C#; measured 155ms/frame in pure Python against an 80ms tick budget — reported by Jerry as "very slow" on the main ANetBBS server and "pretty much unplayable" on a Pi3. New World.compute_light_grid(cam_x, cam_y, vp_w, vp_h, is_day) computes the entire viewport's lighting in one pass: one O(depth) scan per column for skylight instead of per cell, and one pass over the viewport+margin that finds each light source once and splats its falloff onto nearby grid cells, instead of every cell independently re-scanning its neighborhood for sources. _draw_world now calls this once per frame. Measured 5-7ms/frame typical, ~29ms even in a deliberately dense-torch stress test — both comfortably under budget. Verified byte-identical output against the old per-cell method across a full viewport (0 mismatches) before trusting the optimization.
  • FIX: Dropped lava (block 15) from LIGHT_SRC in the Python version — a visible lava sea in the Nether can be hundreds of tiles, each of which would trigger an O(R²) splat; torches/glowstone/portals are naturally sparse and don't have this problem. (Lava is still a light source in the standalone C# door — not an issue there, not changed.)

v1.0b2.16 — ANetCRAFT: full Minecraft-style progression + win condition (July 2026)

  • FEATURE: Ported the same expansion built for the standalone C# ANetCRAFT-Door project into the bundled Python anetbbs/features/anetcraft.py (1582 → 2468 lines), by hand — the two are separate codebases with no shared code. Added: World.light_at() sky+torch/glowstone lighting (hostile spawns now gated on darkness via is_dark(), not day/night, so unlit caves are dangerous at noon); furnace block (id 20) + smelting (SMELT/FUEL tables, iron/gold ore now drop raw ore ids 67/68 instead of ingots directly, sand→glass moved from instant craft to furnace); 4-tier armor system (ids 77-92) with Player.total_defense()/reduced_damage(); lava burn damage + fire_ticks status; a _respawn_player() death cycle that didn't exist before; a Nether dimension (World._gen_nether()) reached by building + lighting an Obsidian portal with Flint and Steel (flood-fill frame validator in _try_ignite_portal), with Blaze and Ghast mobs; Enderman mob → Ender Pearl → Eye of Ender crafting chain; an End dimension (World._gen_end()) reached through a ring of 8 End Portal Frames, hosting a full Ender Dragon boss fight (_dragon_ai) with healing-crystal pillars, ending in a victory screen (_render_victory_overlay) and Dragon Egg trophy. World keeps going after victory.
  • FIX: Pressing Enter in the ANetCRAFT inventory screen never actually swapped items — _handle_key's generic "close overlay" branch intercepted Enter before it ever reached _inv_key's swap logic. The "Enter=grab/drop" hint had been non-functional since this feature was written.
  • FIX: Starving to 1 HP in ANetCRAFT could leave a player stuck there forever — there was no death/respawn handling at all for the zero-HP case.
  • NOTE: Save format changed (dims list of per-dimension world+mobs instead of a single top-level world/mobs). load() auto-migrates old single-dimension saves, so existing player saves on the live server load fine.

v1.0b2.15 — Terminal message wrapping fix + Admin menu reorg (July 2026)

  • FIX: Terminal message reader (board threads, private messages, Inter-BBS instant messages) truncated any line past column 78 (line[:78]) instead of word-wrapping it — long web-composed paragraphs were cut off mid-sentence when read over telnet/SSH. read_thread, the PM viewer, and the InterBBS IM viewer now build their output through _wrap_text() and a new shared _page_lines() pager (extracted from _page_text()), so long messages word-wrap to the session's terminal width and paginate with a --MORE-- prompt instead of truncating.
  • FEATURE: Admin navbar dropdown reorganized from one 58-item flat list into 8 entries (Dashboard + Users/Messages/Files/Content/Network/System/Settings). Each category opens a new /admin/hub/<section> page rendering its tools as cards (ADMIN_HUB_SECTIONS in web/admin.py, templates/admin/hub.html). Purely a navigation change — no existing admin route or URL was moved.

v1.0b2.14 — CP437 encoding fixes: terminal input + web renderer (July 2026)

  • FIX: ANEdit dropped CP437 high-byte characters (ä ö ü Ä Ö Ü ß etc.) entered from SyncTERM/CP437 sessions. Root cause: _Keys decoded raw bytes as latin-1, turning CP437 byte 0x84 (ä) into U+0084 (a C1 control character), which isprintable() rejects. _Keys now uses session.encoding (default cp437) so high bytes decode to their correct Unicode glyphs and are inserted normally. UTF-8 multi-byte buffering also added for SSH sessions.
  • FIX: Web interface rendered correctly stored Unicode characters (ä ö ü ß etc. in local board posts) as CP437 box/line/math symbols. Root cause: render_msg_body_rich always ran a latin-1→CP437 round-trip (e.g. ä U+00E4 → byte 0xE4 → CP437 Σ). Local posts are stored as proper Unicode and need no charset conversion — the round-trip is now skipped when no chrs header is present.