[H] HOMEATARI MUTINEER / DOCUMENTATION / 1.3.183

REMMS Programmers Guide

Use the resident communications API and caller session contract from Atari programs.

Browse documentation

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:

OffsetContents
+0..+2ASCII signature REM
+3not written by REMMS; owned by the 850 R: handler, see below
+4..+5API jump-table address, little-endian
+6..+7public-state address
+8..+9resident-end address
+10..+11session-block address
+12..+13native transfer service table
+14..+15session 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):

AddressMeaningExpected value
PEEK(1000)signature byte 082 (R)
PEEK(1001)signature byte 169 (E)
PEEK(1002)signature byte 277 (M)
PEEK(1003)not REMMS (850 R: handler scratch)do not use; read RPUB+5
PEEK(1004)+256*PEEK(1005)API jump tableassign to RAPI
PEEK(1006)+256*PEEK(1007)public blockassign to RPUB
PEEK(1008)+256*PEEK(1009)resident enddiagnostics only
PEEK(1010)+256*PEEK(1011)session blockassign to SS

Public block offsets from RPUB:

OffsetPEEKMeaning
+0..+4signatureREMMS
+5major1
+6minor7
+7mode0 local, 1 host
+8route0 local, 1 remote, 2 both
+9R: open flag0 closed, nonzero open
+10last CIO statusraw Atari CIO status byte
+11last REMMS errorsee error table below
+12installed flagnonzero after handlers installed
+13last CONSOL valueraw console register sample
+16..+21console/debug bytesraw/current/last/poll/CH/ATACHR
+22/+23event sequenceincrements on recorded events
+24physical statesee physical-state table
+25connection statesee connection-state table
+26last operationsee operation table
+27last CIO status mirrormost recent device status
+28transition0 none, 1 run, 2 resume, 3 close, 4 suspend

Session block offsets from SS:

OffsetPEEK/POKEMeaning
+0magic77
+1version6
+2name length0..40
+3..+42name bytescaller handle
+43address length0..40
+44..+83address bytessecondary identity/email
+84AMODE0 ASCII translation, 1 native ATASCII
+85LMODE0 CR only, 1 CR/LF in ASCII output
+86XMODEexpert/transfer flag used by BASIC
+87online state1 remote online, 8 local sysop handoff
+88snoop state0 remote-only output, 1 local and remote output
+90/+91request keylow/high app key for APPMGR
+92/+93return keylow/high app key for APPMGR
+96transfer/module modemodule-owned scratch
+97call bytemessage/file/door handoff byte
+102current appruntime app handle
+103..+106pending statsmessages/uploads/downloads/calls
+122delayed byteREMMS/BASIC handoff scratch
+123access level0..9
+124/+125user numberhigh/low
+126/+127end minutehigh/low absolute day minute
+128..+138pager config/stateenabled, rows, flags, counters
+139physical statemirror of resident physical owner
+140connection statelocal/wait/ring/answer/connect/lost/error
+141/+142last eventkind/value
+143CIO statuslast status byte
+144text mode0..5, see text table
+145text stateinput field state/counter
+146console gatelocal console de-bounce
+147/+148event sequencelow/high
+149FIFO overflownonzero after overflow
+150open policy0 resume, 1 fresh WFC
+151transitionrun/resume/close/suspend telemetry
+152last operationopen/status/get/put/etc.
+153text choiceone-byte choice buffer
+154WFC actionlocal console action latch
+155exit reasoncarrier/fatal/timeout/application
+156stats pendingnonzero tells WFC to run WFCSTAT
+157config profilenone/wait-call/connected
+158ownership sequenceincrements on physical owner changes
+159answer rings1..9
+160ring countcurrent pre-answer count
+161/+162next/return app handlesruntime handles
+163..+337XFR1 transfer blockexternal transfer request/result
+340..+351MSG1native message operation/result control block
+317..+322message/job cacheunread count, idle minutes, job state/result

BASIC USR Reference

REMMS 1.7 keeps every older API offset stable. Each entry is three bytes:

OffsetCommon nameBASIC callArgumentsReturn
+0RINITUSR(RAPI)none0 OK, error code on failure
+3RMODEUSR(RAPI+3,mode)0 local, 1 host0 OK, 5 bad arg, other error
+6RROUTEUSR(RAPI+6,route)0 local, 1 remote, 2 both0 OK, 5 bad arg, other error
+9ROPENUSR(RAPI+9,policy)0 resume, 1 fresh WFC0 OK, error code on failure
+12RCLOSEUSR(RAPI+12,kind)0 hangup close, 1 suspend for disk I/O0 OK, error code on failure
+15RSTATUSR(RAPI+15)nonecurrent connection state/error code
+18RGETUSR(RAPI+18)nonebyte 0..255 or $010x structured result
+21RPUTUSR(RAPI+21,byte)raw byte 0..2550 OK or $010x structured result
+24RCONUSR(RAPI+24)noneconsole action 0..6
+27RTGETUSR(RAPI+27)nonecanonical text byte or $010x structured result
+30PBEGINUSR(RAPI+30)none0 OK or error code
+33PPUTUSR(RAPI+33,byte)canonical display byte0 OK or error code
+36PENDUSR(RAPI+36)nonepager result code
+39RRESETUSR(RAPI+39)none0 OK, 5 if physical owner busy
+42REVENTUSR(RAPI+42)noneevent_kind*256+value
+45TBEGINUSR(RAPI+45,text_mode)1 line, 2 masked, 3 choice, 4 translated, 5 editor0 OK or 5 bad arg
+48TENDUSR(RAPI+48)none0 OK
+51RFAILUSR(RAPI+51,reason)4 carrier, 6 fatal, 7 timeout, 8 app0 OK or $0103 fatal cleanup
+54RMSGUSR(RAPI+54)nonecurrent connection state/error code
+57RDETACHUSR(RAPI+57)none0 OK or close/restore error
+60RCONCFGUSR(RAPI+60,snoop)0 remote only, 1 local mirror0 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:

DecimalHexMeaning
256$0100no data available
257$0101carrier lost
258$0102recoverable I/O/FIFO condition
259$0103fatal I/O
260$0104timeout

REMMS error codes:

CodeMeaning
0none
1bad mode
2bad route
3missing handler
4R: not open
5bad argument
6CIO failure
7carrier lost
8FIFO overflow
9timeout

Event kinds returned by REVENT:

KindMeaningValue byte
0none0
1textcanonical text byte
2console1 START, 2 SELECT, 3 OPTION
3call stateconnection-state value
4carrier losserror/reason
5recoverableerror/reason
6fatalerror/reason
7timeouterror/reason

Physical states:

CodeMeaning
0closed
1opening
2open
3closing
4resume pending
5fault

Connection states:

CodeMeaning
0local
1wait for call
2ringing
3answering
4connected
5lost
6error

Text input modes for TBEGIN:

ModeMeaningBehavior
0noneno text owner
1lineeditable line, empty return allowed
2maskededitable password, echoes *
3choiceone visible key followed by EOL
4translatedtranslated byte stream without line editor
5editorcanonical 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:

CodeOperation
0none
1OPEN
2XIO 38
3XIO 34
4XIO 36
5XIO 40
6STATUS
7GET
8PUT
9CLOSE
10carrier 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 +87 online, selects route both, and installs its E:/K: wrappers.
  • A remote-only or mirrored route is active only while session +87 is 1.
  • 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 explicit PHYSICAL_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 inputCanonical ATASCII
BEL 07bell FD
BS 08backspace 7E
TAB 09tab 7F
LF 0Aspace 20
VT 0B0B
FF 0Cclear 7D
CR 0DEOL 9B
1C..1Fspaces

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 outputRemote bytes
EOL 9BCR, plus LF when session +85 is enabled
backspace 7EBS 08
tab 7Fspace 20
bell FDBEL 07
clear 7DANSI 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:

ValuePhysical input
0none
1START or keyboard L
2SELECT or keyboard S
3OPTION 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:

CodeKeyAction
1STARTlocal sysop login
2SELECToffline tools
3OPTIONshutdown to BASIC
4HELPtoggle snooping for this call
5Shift+HELPtoggle persistent sysop availability
6Ctrl+HELPenter 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.

OffsetMeaning
+0..+1magic/version
+2..+42caller handle length and text
+43..+83secondary identity length and text
+84..+86ATASCII, line-feed, and transfer/expert modes
+87online state: 1 remote, 8 local-console handoff
+88current snoop state
+90..+93requested and return stable app keys
+96transfer/module mode
+97current message/file/door handoff byte
+102current application handle
+123access level
+124..+125user number, high/low
+126..+127session end minute, high/low
+139..+154physical/connection/event/CIO/text/transition telemetry
+155..+156exit reason and pending-stat flag
+157..+158configuration profile and ownership sequence
+159..+160answer-rings policy and current ring count
+161..+162next/return runtime app handles
+163..+337XFR1 transfer / FST1 file-store workspace
+317..+322unread-count and scheduler fields while transfer is inactive
+338..+339transfer mark-session token
+340..+351MSG1 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).