Synchronet door compatibility
Status of the ANetBBS Synchronet .js door compatibility shim
(anetbbs/games/synchronet_compat.py — generates a Node.js wrapper
that's prepended to the door script).
The "real" path: if jsexec is on the host (typically at
/sbbs/exec/jsexec on a Synchronet install, or via SBBS_JSEXEC env
override), we use it directly. That gives 100% Synchronet API coverage.
The "shim" path: when no jsexec is found, we fall back to Node + our
compat. This doc tracks how much of Synchronet's API the shim covers.
What's known to work
These door categories are confirmed running through the shim:
| Door | Status | Notes |
|---|---|---|
| LORD (Synchronet JS port) | ✅ Working end-to-end (v287) | Bundled in anetbbs/games/sbbs_doors/lord/. Full play: character creation, town square, Inn, Violet flirt, save/load roundtrip, multi-session persistence. |
| BotWars | ✅ Working | Bundled in vendor/games/botwars/ |
dd_lightbar_menu doors |
✅ Working | Lightbar UI works, mouse_getkey covered |
Doors that only use console.* for I/O |
✅ Working | The largest single category |
| ANetSIMS | ✅ Working | Bundled. Original ANET door. |
API surface (post-v1.0b coverage)
| Top-level object | Fields shimmed | Synchronet has | Coverage |
|---|---|---|---|
console |
~60 | ~80 | ~75% |
bbs |
~22 | ~80 | ~28% |
system |
~14 | ~50 | ~28% |
user |
~30 | ~50 | ~60% |
js |
~10 | ~12 | ~85% |
server |
stub | rich | minimal (enough for dorkit's sbbs mode) |
client |
stub | rich | minimal (enough for dorkit's sbbs mode) |
File class |
read/write/seek/lock/unlock/flush/truncate/iniGet/iniSet/readBin/writeBin/readStr/writeStr/position | full | ~85% |
Queue class |
full (name-cached so two new Queue("foo") calls share state, matching real Synchronet IPC) |
full | done |
Notable v287 additions for LORD
Queue— name-cached, drives dorkit input viadk.console.input_queue_callbackon a single-threaded Node event loopstrftime(fmt, unix_seconds)— full C-strftime specfile_mutex(filename, contents?)— single-node always-grant + persistrequire()accepts the scope-prefix formrequire(scope, "file.js", "Obj")load(true, "file.js", args)background form returns a stub Queueload_path_list,on_exit(code),js.exec(),js.gc,js.global,
js.terminate_signaledFile.readBin/writeBin/readStr/writeStr— fixed-width LE int + string I/OFile.lock/unlock/flush/truncate— locks always-grant (single node);
truncate persists to diskFile.open(mode)parses the full fopen mode language (r/w/a/r+/w+/a+,
bignored)- Critical
File.write(str, len)overwrites at_pos(not append-to-EOF) console.right/left/up/down(n)aliases (sbbs_console.js doesn't use the
cursor_*names)- Load resolver prefers
<stubs_dir>/dorkit/<file>over flat
<stubs_dir>/<file>so dorkit's Screen / Graphic land with the right
prototype methods - Node-friendly
sbbs_input.jsreplacement: callback on
dk.console.input_queue_callback, one-timestty min 0 time 1,
no per-iteration thrash
The shim also exposes vendored Synchronet .js libraries from
anetbbs/games/sbbs_stubs/ (174 files) when a door does
load("dd_lightbar_menu.js") etc. The upstream xtrn/dorkit/ subtree
ships under sbbs_stubs/dorkit/.
The shim also exposes vendored Synchronet .js libraries from
anetbbs/games/sbbs_stubs/ (174 files) when a door does
load("dd_lightbar_menu.js") etc. So heavy lifting is sometimes done
by the libraries themselves.
What's missing — and what it blocks
Priority is "how often do real doors hit it":
High priority (many doors fail without these)
bbs.exec()— runs another.jsdoor from inside one. Doors that
chain into sub-modules fail.bbs.menu()— Synchronet's built-in menu engine. Used by hub-style
door packs.user.securitycomplete — onlylevelis shimmed. Doors that
checkflags1/flags2/exemptetc. for access gates fail.user.stats.*— call/post/upload counters. Doors that pull these
for ranking displays show zeros.
Medium priority (some doors fail)
msg_area.*— message-base access. Doors that announce their
results to a board (high-score broadcasts, etc.) silently fail to
post. Doors that READ message bases (mail readers ported as doors)
don't work at all.file_area.*— file-base access. Same pattern as above for file
echoes.xbase/recordfile— Synchronet's primitive record-file API.
Some older doors store their state via this.bbs.start_xfer*— file transfer. Doors that hand-off into a
Z-Modem upload won't.client.socket— direct socket access. Only matters for
doors that talk to remote servers from inside the door (rare).
Low priority (edge cases)
bbs.menu_*state-mutation functions (we don't have a Synchronet
menu, so most of these are no-ops anyway).bbs.start_conf/bbs.end_conf— conference switching.bbs.goto_xtrn(slug)— would need to bridge into our
Game.query.filter_by(slug=…)loader.
Survey of common Synchronet xtrn doors
This is a recommended testing matrix — pick a handful, install them
through the shim, see what happens. The Synchronet xtrn directory is
at https://github.com/SynchronetBBS/sbbs/tree/master/xtrn.
| Door | Likely status with shim | Reason |
|---|---|---|
oneliner (oneliner.js) |
✅ Should work | Pure console.* + File |
slyedit |
✅ Should work | Editor — uses console + load() |
slyvote |
✅ Should work | Self-contained vote system |
digdist |
⚠️ Probably broken | Heavy bbs.* use |
bbslist |
⚠️ Mixed | Reads/writes bbslist.json via File — should work; bbs.exec to chain into sub-views might break |
chksetup |
❌ Won't work | Synchronet-internal setup checker |
msgmaint |
❌ Won't work | Touches msg_area heavily |
pdo (post-door object) |
❌ Won't work | xbase-based |
tw2002 (TradeWars JS) |
⚠️ Untested | xbase storage; would need testing |
lord-style doors |
⚠️ Mixed | Most use console + user.handle only |
How to extend the shim
Add a new function in anetbbs/games/synchronet_compat.py:
- Find the top-level object you want to extend (
console = { … }). - Add the field name + a Node implementation that mimics Synchronet's
behaviour. Synchronet docs at http://wiki.synchro.net/jsobj:start. - If the function maps to BBS data the user runs as has access to, hit
the Flask app context (the shim is generated per-launch so it sees
the user / game / node).
For frequently-needed missing pieces, prefer to use a real jsexec —
much less work than re-implementing Synchronet's runtime.
Recommended test pack
Once you've added a door, send a real user through Admin → Door Games
→ <slug> → Test Launch to see the JS error trace if anything blows up.
The shim wraps the user script in try/catch and prints the stack,
so silent-fail is rare.