01 / A FUNCTION YOU SUPPLY
The argument is a question for your ML routine.
USR(x) is a BASIC function whose implementation you supply in machine language. BASIC evaluates the argument, passes a numeric value in its floating-point accumulator, FAC1, and calls the installed routine. The usual numeric path returns a value in FAC1 for BASIC to use in the surrounding expression.
For AIS4CBM, we give the argument a particular meaning: the field we want from the currently locked vessel. With the vessel reader selected, USR(2) asks for the MMSI, USR(5) asks for speed over ground, and USR(34) asks for the vessel name.
SYS is useful for actions and register-based calls. USR is useful when ML supplies a value that BASIC can assign, print or use in a calculation. In this application the two cooperate: a SYS call selects and prepares the vessel; USR calls read its fields.
BASIC’s numeric calling convention is described in the Commodore 128 System Guide, section 18.42. AIS4CBM’s string-return extension is explained below.
02 / CONNECT BASIC TO THE RIGHT ROUTINE
One USR entry, a selected operation.
On the C128, $1219/$121A hold the low and high bytes of the USR target address, in the jump entry beginning at $1218. Our installer points that entry at usrDelegate, a short bridge that handles the bank switch and return:
setDelegateUSR:
lda #<usrDelegate
sta $1219
lda #>usrDelegate
sta $121a
rts
After a successful vessel lock, getVesselDataImpl selects getData as the routine behind that bridge:
lda #<getData
sta usrDelegate.usrFarLo
lda #>getData
sta usrDelegate.usrFarHi
jsr setDelegateUSR
usrFarLo and usrFarHi name the two address bytes of a JSR instruction inside the bridge. Updating them makes the next USR call reach the selected operation.
This selection matters. The time API selects a different handler: after SYS GT, the viewer uses USR(1) and USR(2) for time and date. Locking a vessel selects the field reader again. Keep a group of vessel reads together; do not select another USR service halfway through it.
Installer, vessel API and time API · Mapping the Commodore 128: USR jump entry
03 / GIVE THE READER A VESSEL
Lock once. Read the fields you need.
There are two different identifiers in this transaction. A record ID chooses the vessel; a field selector chooses a value from it. The SYS call receives the record ID as low and high bytes. Each following USR call receives only a field selector.
The lock/fetch operation loads the vessel’s metadata and available payloads from REU into the runtime’s scratch area. It also selects the appropriate field table for that vessel class. USR reads from this fetched data, so BASIC does not have to repeat a REU fetch for every field.
1000 BANK 0:SYS GV,1,LO,HI:RREG A,,,P
1010 IF (P AND 1)<>0 THEN RETURN
1020 MM=USR(2)
1030 SYS GV,4,LO,HI
1040 RETURN
This teaching fragment assumes an initialized runtime, GV=DEC("8022"), and a valid record ID already split into LO/HI. It is not a standalone program. RREG captures the returned processor status in P; bit 0 is carry. If the lock fails, the subroutine returns without reading or unlocking, and MM is unchanged.
Action 4 releases a read-only lock without persisting visual metadata. A display-update handler uses action 0 when it has consumed and updated that metadata; that path also acknowledges the vessel-message event. The fragment above performs no event acknowledgement. See the API transaction contract for the distinction.
The real viewer reads several fields together.
360 NM$=USR(34):IM=USR(6):CS$=USR(24):DE$=USR(8):DR=USR(14)/10:VT=USR(30)
The surrounding dialog has already locked the vessel and checked for static data before it calls this handler. The assignments retrieve the name, IMO number, callsign, destination, draught and ship type. BASIC remains responsible for formatting these values for display.
| Selector | Field | BASIC use |
|---|---|---|
2 | MMSI | Numeric identity, such as MM=USR(2). |
5 | Speed over ground | Class A/B display: USR(5)/10 converts tenths of a knot. |
6 | IMO number | IM=USR(6) in the Class A static handler. |
24 | Callsign | String: CS$=USR(24). |
34 | Name | String: NM$=USR(34). |
35 | Course over ground | USR(35)/10 converts tenths of a degree. |
Use the selectors and field meanings for the matching build and vessel class. USR returns the stored field value; scaling and AIS “not available” values still need interpretation. When the current reader cannot find a field or its payload is absent, it returns numeric zero. That is not automatically an empty string or proof of a measured zero, so check availability before assigning a string field.
Actual dialog, availability checks and field reads · Field definitions and BASICFieldRef generation
04 / ONE QUESTION, TWO KINDS OF ANSWER
Numbers use FAC. Names need a BASIC string.
getData converts the USR argument from FAC1 to a field index. The field table identifies the source and offset in the locked record, and the descriptor identifies its size and type. MMSI comes from the registry scratch record; positional and static fields come from the fetched payload scratch.
A numeric result replaces the numeric argument.
For a numeric field, getDataByField copies the decoded bytes into tempStore, then calls trans to convert them to FAC1. The value that entered FAC1 was the field selector; the value leaving FAC1 is the answer. For example, a Class A/B speed field containing 123 becomes BASIC’s number 123, which the viewer divides by 10 to display 12.3 knots.
The numeric path returns with carry clear. Our integer-to-FAC tutorial follows the conversion for a larger value such as an MMSI.
A string requires more than a pointer.
For a text field, stringHandler determines the useful length, allocates space through BASIC’s string routines, copies the characters into the Bank 1 string area, and prepares the string descriptor. It then returns with carry set.
That carry bit is our bridge’s convention. It is not a general BASIC feature that turns any USR routine into a string-returning function. The delegate recognizes it and takes a different return path:
usrDelegate:
{
lda $ff00
sta initialBank+1
lda #BANK0
sta $FF00
.label usrFarLo = *+1
.label usrFarHi = *+2
jsr $0000
bcc !+
pla
pla
initialBank:
!: lda #$ff
sta $ff00
rts
}
With carry clear, the bridge restores the bank and returns normally. With carry set, the two PLA instructions discard one saved return address from the stack before returning. Together with the prepared BASIC string descriptor, this is the interpreter-specific return path that makes NM$=USR(34) work. The stack adjustment belongs to this USR bridge; it is not a general string-return recipe for arbitrary ML callers.
getData, fieldSelector, getDataByField and stringHandler · Numeric conversion · USR bridge
05 / TWO SMALL FEATURES, ONE CLEAN HANDOFF
WAIT says when. USR supplies the value.
Our BASIC interface waits for a relevant notification, obtains its vessel record ID, locks that record through SYS, reads the required fields through USR, and releases the record. ML owns the decoded storage; BASIC works with values that fit naturally into its expressions.
- Install before calling. USR needs a valid target and the runtime’s memory setup.
- Select the operation. The same USR syntax can reach the vessel reader or another service, depending on the active delegate.
- Keep the transaction together. Lock, check success, read available fields, then unlock the same record.
- Return the type BASIC expects. FAC handles numbers; our string path also prepares a descriptor and adjusts the return through the interpreter.
Read the code and the companion explanations
- An event loop with BASIC WAIT — when BASIC should act.
- One loop, two languages: IGONE — how ML gets its foreground execution turns.
- Developer API — calls, record transactions and shared events.
- basicinterface.asm — the complete vessel field reader.
Assembly excerpts retain the current instructions, with comments omitted and spacing simplified. The short lock/read/release subroutine is a teaching example; line 360 is from the actual viewer. GitLab links follow main.