REMMS Programmers Guide
REMMS is the supported resident modem, input, output-routing, and session layer for Atari Mutineer. It is not an MMS ABI clone. BASIC code discovers REMMS dynamically and calls the published jump table.
REMMS.COM loads at $1E00, installs through INITAD, publishes a discovery
record, and raises MEMLO so Atari BASIC modules can call it safely.
Discovery Record
The loader publishes this record at $03E8:
| Offset | Contents |
|---|---|
+0..+2 | ASCII signature REM |
+3 | not written by REMMS; owned by the 850 R: handler, see below |
+4..+5 | API jump-table address, little-endian |
+6..+7 | public-state address |
+8..+9 | resident-end address |
+10..+11 | session-block address |
+12..+13 | native transfer service table |
+14..+15 | session transfer block (SS+163) |
Atari BASIC therefore reads the API pointer from decimal 1004/1005, the
public block from 1006/1007, and the session block from 1010/1011.
Byte +3 ($03EB) is the XL OS CARTCK location. REMMS does not own it and
never writes it: the 850 R: handler, relocated just above REMMS at MEMLO, uses
it as working storage and rewrites it thousands of times per call. The publish
loop skips it. The remms-lifecycle case discovery_after_connect traces every
write to the other discovery bytes under the Altirra debugger and watches
REMMS's only store into the record (sta REMMS_DISCOVERY,x) for index 3. It
fails if REMMS writes $03EB, if anything but REMMS writes another discovery
byte, or if the signature, pointers or RPUB+5/+6 change after a connected
call. The other
discovery bytes are XL OS boot-time variables (SUPERF, CKEY, CASSBT,
DERRF, ACMVAR) that nothing writes once DOS is running; the layout is
inherited from the MMS memory map. Read the REMMS version from the public block
(RPUB+5 major, RPUB+6 minor).
BASIC PEEK Reference
Discovery record at decimal 1000 ($03E8):
| Address | Meaning | Expected value |
|---|---|---|
PEEK(1000) | signature byte 0 | 82 (R) |
PEEK(1001) | signature byte 1 | 69 (E) |
PEEK(1002) | signature byte 2 | 77 (M) |
PEEK(1003) | not REMMS (850 R: handler scratch) | do not use; read RPUB+5 |
PEEK(1004)+256*PEEK(1005) | API jump table | assign to RAPI |
PEEK(1006)+256*PEEK(1007) | public block | assign to RPUB |
PEEK(1008)+256*PEEK(1009) | resident end | diagnostics only |
PEEK(1010)+256*PEEK(1011) | session block | assign to SS |
Public block offsets from RPUB:
| Offset | PEEK | Meaning |
|---|---|---|
+0..+4 | signature | REMMS |
+5 | major | 1 |
+6 | minor | 7 |
+7 | mode | 0 local, 1 host |
+8 | route | 0 local, 1 remote, 2 both |
+9 | R: open flag | 0 closed, nonzero open |
+10 | last CIO status | raw Atari CIO status byte |
+11 | last REMMS error | see error table below |
+12 | installed flag | nonzero after handlers installed |
+13 | last CONSOL value | raw console register sample |
+16..+21 | console/debug bytes | raw/current/last/poll/CH/ATACHR |
+22/+23 | event sequence | increments on recorded events |
+24 | physical state | see physical-state table |
+25 | connection state | see connection-state table |
+26 | last operation | see operation table |
+27 | last CIO status mirror | most recent device status |
+28 | transition | 0 none, 1 run, 2 resume, 3 close, 4 suspend |
Session block offsets from SS:
| Offset | PEEK/POKE | Meaning |
|---|---|---|
+0 | magic | 77 |
+1 | version | 6 |
+2 | name length | 0..40 |
+3..+42 | name bytes | caller handle |
+43 | address length | 0..40 |
+44..+83 | address bytes | secondary identity/email |
+84 | AMODE | 0 ASCII translation, 1 native ATASCII |
+85 | LMODE | 0 CR only, 1 CR/LF in ASCII output |
+86 | XMODE | expert/transfer flag used by BASIC |
+87 | online state | 1 remote online, 8 local sysop handoff |
+88 | snoop state | 0 remote-only output, 1 local and remote output |
+90/+91 | request key | low/high app key for APPMGR |
+92/+93 | return key | low/high app key for APPMGR |
+96 | transfer/module mode | module-owned scratch |
+97 | call byte | message/file/door handoff byte |
+102 | current app | runtime app handle |
+103..+106 | pending stats | messages/uploads/downloads/calls |
+122 | delayed byte | REMMS/BASIC handoff scratch |
+123 | access level | 0..9 |
+124/+125 | user number | high/low |
+126/+127 | end minute | high/low absolute day minute |
+128..+138 | pager config/state | enabled, rows, flags, counters |
+139 | physical state | mirror of resident physical owner |
+140 | connection state | local/wait/ring/answer/connect/lost/error |
+141/+142 | last event | kind/value |
+143 | CIO status | last status byte |
+144 | text mode | 0..5, see text table |
+145 | text state | input field state/counter |
+146 | console gate | local console de-bounce |
+147/+148 | event sequence | low/high |
+149 | FIFO overflow | nonzero after overflow |
+150 | open policy | 0 resume, 1 fresh WFC |
+151 | transition | run/resume/close/suspend telemetry |
+152 | last operation | open/status/get/put/etc. |
+153 | text choice | one-byte choice buffer |
+154 | WFC action | local console action latch |
+155 | exit reason | carrier/fatal/timeout/application |
+156 | stats pending | nonzero tells WFC to run WFCSTAT |
+157 | config profile | none/wait-call/connected |
+158 | ownership sequence | increments on physical owner changes |
+159 | answer rings | 1..9 |
+160 | ring count | current pre-answer count |
+161/+162 | next/return app handles | runtime handles |
+163..+337 | XFR1 transfer block | external transfer request/result |
+340..+351 | MSG1 | native message operation/result control block |
+317..+322 | message/job cache | unread count, idle minutes, job state/result |
BASIC USR Reference
REMMS 1.7 keeps every older API offset stable. Each entry is three bytes:
| Offset | Common name | BASIC call | Arguments | Return |
|---|---|---|---|---|
+0 | RINIT | USR(RAPI) | none | 0 OK, error code on failure |
+3 | RMODE | USR(RAPI+3,mode) | 0 local, 1 host | 0 OK, 5 bad arg, other error |
+6 | RROUTE | USR(RAPI+6,route) | 0 local, 1 remote, 2 both | 0 OK, 5 bad arg, other error |
+9 | ROPEN | USR(RAPI+9,policy) | 0 resume, 1 fresh WFC | 0 OK, error code on failure |
+12 | RCLOSE | USR(RAPI+12,kind) | 0 hangup close, 1 suspend for disk I/O | 0 OK, error code on failure |
+15 | RSTAT | USR(RAPI+15) | none | current connection state/error code |
+18 | RGET | USR(RAPI+18) | none | byte 0..255 or $010x structured result |
+21 | RPUT | USR(RAPI+21,byte) | raw byte 0..255 | 0 OK or $010x structured result |
+24 | RCON | USR(RAPI+24) | none | console action 0..6 |
+27 | RTGET | USR(RAPI+27) | none | canonical text byte or $010x structured result |
+30 | PBEGIN | USR(RAPI+30) | none | 0 OK or error code |
+33 | PPUT | USR(RAPI+33,byte) | canonical display byte | 0 OK or error code |
+36 | PEND | USR(RAPI+36) | none | pager result code |
+39 | RRESET | USR(RAPI+39) | none | 0 OK, 5 if physical owner busy |
+42 | REVENT | USR(RAPI+42) | none | event_kind*256+value |
+45 | TBEGIN | USR(RAPI+45,text_mode) | 1 line, 2 masked, 3 choice, 4 translated, 5 editor | 0 OK or 5 bad arg |
+48 | TEND | USR(RAPI+48) | none | 0 OK |
+51 | RFAIL | USR(RAPI+51,reason) | 4 carrier, 6 fatal, 7 timeout, 8 app | 0 OK or $0103 fatal cleanup |
+54 | RMSG | USR(RAPI+54) | none | current connection state/error code |
+57 | RDETACH | USR(RAPI+57) | none | 0 OK or close/restore error |
+60 | RCONCFG | USR(RAPI+60,snoop) | 0 remote only, 1 local mirror | 0 OK, 5 bad arg |
RTEXT is the fixed page-six entry at $0601 rather than a resident jump-table
slot:
R=USR(1537,ADR(PATH$),LEN(PATH$),ADR(WORK$),FLAGS)
WORK$ reserves 256 bytes. Its first 64 bytes hold the EOL-terminated path and
the remaining 192 bytes are the streaming buffer. Flag bit 0 clears the display
before output and bit 1 enables paging. Results are 0 success, 1 user abort,
2 invalid request, 3 open failure, 4 disk failure, and 5 output or
carrier failure. The service preserves the current route, suspends the modem
around each disk chunk, and resumes it before sending that chunk.
RDETACH ends resident text/pager activity, closes REMMS-owned R: state,
restores the exact saved E: and R: HATABS pointers, leaves K: untouched,
and returns routing to local. REMMS discovery and memory remain present;
RINIT installs the handlers again.
Set the common names once after discovery:
1010 RINIT=RAPI:RMODE=RAPI+3:RROUTE=RAPI+6:ROPEN=RAPI+9:RCLOSE=RAPI+12
1020 RSTAT=RAPI+15:RGET=RAPI+18:RPUT=RAPI+21:RCON=RAPI+24:RTGET=RAPI+27
1030 PBEGIN=RAPI+30:PPUT=RAPI+33:PEND=RAPI+36:RRESET=RAPI+39
1040 REVENT=RAPI+42:TBEGIN=RAPI+45:TEND=RAPI+48:RFAIL=RAPI+51
Result And Enum Tables
Structured byte results returned by RGET, RTGET, and RPUT:
| Decimal | Hex | Meaning |
|---|---|---|
256 | $0100 | no data available |
257 | $0101 | carrier lost |
258 | $0102 | recoverable I/O/FIFO condition |
259 | $0103 | fatal I/O |
260 | $0104 | timeout |
REMMS error codes:
| Code | Meaning |
|---|---|
0 | none |
1 | bad mode |
2 | bad route |
3 | missing handler |
4 | R: not open |
5 | bad argument |
6 | CIO failure |
7 | carrier lost |
8 | FIFO overflow |
9 | timeout |
Event kinds returned by REVENT:
| Kind | Meaning | Value byte |
|---|---|---|
0 | none | 0 |
1 | text | canonical text byte |
2 | console | 1 START, 2 SELECT, 3 OPTION |
3 | call state | connection-state value |
4 | carrier loss | error/reason |
5 | recoverable | error/reason |
6 | fatal | error/reason |
7 | timeout | error/reason |
Physical states:
| Code | Meaning |
|---|---|
0 | closed |
1 | opening |
2 | open |
3 | closing |
4 | resume pending |
5 | fault |
Connection states:
| Code | Meaning |
|---|---|
0 | local |
1 | wait for call |
2 | ringing |
3 | answering |
4 | connected |
5 | lost |
6 | error |
Text input modes for TBEGIN:
| Mode | Meaning | Behavior |
|---|---|---|
0 | none | no text owner |
1 | line | editable line, empty return allowed |
2 | masked | editable password, echoes * |
3 | choice | one visible key followed by EOL |
4 | translated | translated byte stream without line editor |
5 | editor | canonical bytes without automatic echo; the application interprets cursor, insert, and delete keys |
Editor mode leaves received editing controls out of the display stream. The application redraws accepted text explicitly, so ATASCII cursor controls and ANSI cursor sequences cannot be recorded as message-body bytes.
Operation codes stored in telemetry:
| Code | Operation |
|---|---|
0 | none |
1 | OPEN |
2 | XIO 38 |
3 | XIO 34 |
4 | XIO 36 |
5 | XIO 40 |
6 | STATUS |
7 | GET |
8 | PUT |
9 | CLOSE |
10 | carrier STATUS/sense |
RGET/RPUT return $0100 no data, $0101 carrier lost, $0102
recoverable I/O, $0103 fatal I/O, or $0104 timeout. REVENT returns
event_kind*256+value. RFAIL accepts reason 4 carrier, 6 fatal I/O, 7
timeout, or 8 application failure. It preserves a more specific resident
reason, ends text input, and closes through the lifecycle owner.
Modes, Routes, And HATABS
The central invariant is: local mode never polls, reads, or writes R:.
- Local mode forces local output and restores the original
E:/K:HATABS entries. - Host-open begins in local-output mode while REMMS waits for a call.
- After
CONNECT, REMMS marks session+87online, selects routeboth, and installs itsE:/K:wrappers. - A remote-only or mirrored route is active only while session
+87is1. - Carrier loss or fatal modem I/O clears online/text state, closes
R:through the physical owner, restores local mode/route, and records one pending event. Cleanup CLOSE failure leaves explicitPHYSICAL_FAULT.
The wrappers chain to the saved handlers directly. A local E: get first
restores the saved E: entry in HATABS, because SDX's console handler resolves
E: through HATABS again and would otherwise re-enter the wrapper. Every
chained return reinstalls the wrappers. HATABS entries are found by device
letter each time; the saved indexes are transient session bytes that a BASIC
program switch clears.
REMMS preserves the zero-page working range, DVSTAT, and the SIO DCB around
the relevant CIO calls.
Call Lifecycle
Opening R: configures concurrency and resets the host parser.
USR(RSTAT) consumes modem result bytes in bounded batches and recognizes:
RING -> ATA -> CONNECT
Session +159 contains the answer-after-rings setting (1..9) and +160
contains the current ring count. REMMS sends ATA once when the configured
count is reached. The parser stops on the complete CONNECT terminator and
queues every already-available caller byte in order. RING, CONNECT, and
pre-connect NO CARRIER parsing is disabled after connection. Connected
carrier loss is sensed out of band through the 850 status/sense path; negative
CIO status and timeout have distinct structured results and exit reasons.
Raw And Text Input
RGET (RAPI+18) and RPUT (RAPI+21) are byte-exact. WFC uses raw input
for the first-return terminal probe. Native transfer protocols use the REMMS
transfer service table published in discovery bytes +12..+15.
Normal BBS and door input uses RTGET (RAPI+27). Remote E:/K: reads use
the same translated path, so there is one text conversion implementation.
When session +84 is zero, the corrected MOE input table is:
| ASCII input | Canonical ATASCII |
|---|---|
BEL 07 | bell FD |
BS 08 | backspace 7E |
TAB 09 | tab 7F |
LF 0A | space 20 |
VT 0B | 0B |
FF 0C | clear 7D |
CR 0D | EOL 9B |
1C..1F | spaces |
Other bytes are unchanged. CRLF and CRNUL are returned as one EOL; a lone LF
still maps to a space. Ctrl-C 03 and Ctrl-S 13 are discarded. When session
+84 is nonzero, ATASCII bytes pass through unchanged except for those two
remote flow-control bytes.
The resident pager never uses printable letters as controls. At a pager prompt,
Return advances one line, Space advances one page, Ctrl-Q shows the remainder,
and Ctrl-C quits the listing. During output, Ctrl-S pauses, Ctrl-Q resumes, and
Ctrl-C aborts. Printable S, C, and Q are preserved as ordinary typeahead
for the next text input operation.
Standard line editors consume only the canonical values: 7E performs
destructive backspace, 7F inserts one space, and unsupported clear, cursor,
line insert/delete, and character insert/delete controls are ignored. Strict
MOE semantics mean incoming ASCII DEL 7F is indistinguishable from ATASCII
tab and therefore inserts one space; it is not a backspace alias.
Translated input is bounded by a 60-byte watermark in a 64-byte FIFO. A modem
status snapshot of 60 bytes or more is not partially delivered: REMMS sends
XOFF, clears the FIFO, rebuilds the concurrent stream, and returns recoverable
event kind 5, value 8. Every BASIC field reader restarts from an empty
field. This intentionally rejects an unpaced long paste rather than accepting
a rotated or truncated line.
Text Output
Wrapped E: output is raw in ATASCII mode. In ASCII mode REMMS maps:
| ATASCII output | Remote bytes |
|---|---|
EOL 9B | CR, plus LF when session +85 is enabled |
backspace 7E | BS 08 |
tab 7F | space 20 |
bell FD | BEL 07 |
clear 7D | ANSI ESC[2J ESC[H |
The FD mapping intentionally fixes the duplicate-7D comparison in the MOE
source. Binary output must bypass E: and use raw RPUT.
External transfer ownership
FILEBASE writes an XFR1 request into session +163..+337, XFERSEL selects a
registered protocol, and APPMGR loads the native COM file from D1:>PROTO>.
REMMS publishes the native transfer service table at discovery +12/+13 and
the transfer block pointer at +14/+15. Protocol binaries use that table for
raw bytes, binary-mode lifecycle, progress, cancellation, and result state.
Caller-facing BASIC must not overlap disk CIO with an active physical R:
owner. Use RCLOSE,1, perform bounded disk I/O, close the disk channel, and
then call ROPEN,0 before pager or prompt output. Buffer or chunk long
displays; never leave a disk file open while waiting for caller input.
RCLOSE,1 sends XOFF and keeps remote input paused across disk work. The next
TBEGIN discards paused typeahead and sends XON only after the new field is
ready. Callers must not substitute fixed sleeps for this handshake.
Console Commands
USR(RAPI+24) returns:
| Value | Physical input |
|---|---|
0 | none |
1 | START or keyboard L |
2 | SELECT or keyboard S |
3 | OPTION or keyboard ! |
WFC maps these values to local sysop login, offline tools, and shutdown.
Console actions use only the physical console switches and XL/XE HELP latch:
| Code | Key | Action |
|---|---|---|
1 | START | local sysop login |
2 | SELECT | offline tools |
3 | OPTION | shutdown to BASIC |
4 | HELP | toggle snooping for this call |
5 | Shift+HELP | toggle persistent sysop availability |
6 | Ctrl+HELP | enter or leave sysop chat |
REMMS clears HELPFG after consuming $11, $51, or $91. It never reads
ordinary CH or K: input for console control, and there are no printable
aliases.
Session Block
REMMS owns a 352-byte block with magic 77 and session version 9.
| Offset | Meaning |
|---|---|
+0..+1 | magic/version |
+2..+42 | caller handle length and text |
+43..+83 | secondary identity length and text |
+84..+86 | ATASCII, line-feed, and transfer/expert modes |
+87 | online state: 1 remote, 8 local-console handoff |
+88 | current snoop state |
+90..+93 | requested and return stable app keys |
+96 | transfer/module mode |
+97 | current message/file/door handoff byte |
+102 | current application handle |
+123 | access level |
+124..+125 | user number, high/low |
+126..+127 | session end minute, high/low |
+139..+154 | physical/connection/event/CIO/text/transition telemetry |
+155..+156 | exit reason and pending-stat flag |
+157..+158 | configuration profile and ownership sequence |
+159..+160 | answer-rings policy and current ring count |
+161..+162 | next/return runtime app handles |
+163..+337 | XFR1 transfer / FST1 file-store workspace |
+317..+322 | unread-count and scheduler fields while transfer is inactive |
+338..+339 | transfer mark-session token |
+340..+351 | MSG1 message service control block |
BASIC programs must preserve the fields they do not own across RUN.
BASIC Startup Pattern
1000 IF PEEK(1000)<>82 OR PEEK(1001)<>69 OR PEEK(1002)<>77 THEN RUN "D1:>CORE>WFC"
1010 RAPI=PEEK(1004)+256*PEEK(1005)
1020 RPUB=PEEK(1006)+256*PEEK(1007):SS=PEEK(1010)+256*PEEK(1011)
1030 RMODE=RAPI+3:RROUTE=RAPI+6:ROPEN=RAPI+9
1040 RSTAT=RAPI+15:RGET=RAPI+18:RPUT=RAPI+21:RTGET=RAPI+27
1050 IF PEEK(SS)<>77 OR PEEK(SS+1)<>9 THEN RUN "D1:>CORE>RUNBBS"
1060 R=USR(ROPEN,0):IF R<>0 THEN R=USR(RAPI+51,6):RUN "D1:>CORE>WFC"
1070 R=USR(RMODE,1):R=USR(RROUTE,2)
Local modules instead call USR(RMODE,0) and USR(RROUTE,0).