THE PURPOSE
Reuse the engine. Build your own interface.
AIS4CBM’s BASIC 7 text viewer and BASIC 8 graphics viewer already share the same machine-language foundation. Serial reception, AIS decoding, the vessel registry, REU storage and map filtering live below the presentation layer. The API makes that separation available to other applications.
Here, an application programming interface means a set of entry addresses with defined inputs and results. BASIC reaches them through SYS; assembly can use JSR; another language can use a small register-and-memory bridge. The caller does not have to be written in BASIC.
The current interface grew out of the BASIC viewers. Some entries are direct register calls; others prepare a BASIC USR() operation or manage BASIC 8 graphics state. Reusing the binaries from another language is possible, but a new frontend must respect those dependencies and the C128 memory layout.
Start with the repository README, then inspect the exported API implementation. This is a local C128 binary interface; the existing binaries contain 8502 code.
THE CALLING CONTRACT
An address is only half an API.
The public entries occupy the BasicApi segment beginning at $8000. These small wrappers give callers known entry points while the implementation behind them can move. Use the symbols and modules from the same build: out/buildBASIC8.sym is the address authority.
The current standard build uses SwiftLink-compatible serial input and the REU layout for 512 KiB or larger units. A replacement frontend must preserve the runtime’s reserved memory and hardware contracts as well as its call addresses.
- Registers carry arguments. Each call defines its own use of
A,X,Yand carry. Do not assume registers or flags survive unless the routine promises it. - Pointer getters return A/X.
Ais the low byte andXthe high byte. The result is an address, not the counter or record value stored there. - Memory mapping is part of the contract. A native caller must make the runtime code and its working memory visible, with the expected MMU and zero-page setup. A bare
JSRfrom an arbitrary bank is insufficient. - Calls belong in foreground code. The runtime shares scratch buffers, delegates and banking helpers. Treat these interfaces as non-reentrant, and keep one owner of the vessel-access transaction.
// Called after runtime initialization, in its valid memory mapping.
jsr $8004 // getNotifyFlagPointer
sta eventAddress // low byte
stx eventAddress+1 // high byte
// Continue in the caller; eventAddress is its own two-byte variable.
This is the same entry used by BASIC. The caller’s bridge changes; the meaning of the API result stays the same. Prefer a getter to copying a current internal address into your application.
THE MAIN DATA TRANSACTION
Lock. Read. Release.
The application identifies a vessel by a 16-bit record ID, currently 0–999. This is a registry handle, not a Bank 0 pointer and not an MMSI. Records live in Bank 1; each owns a fixed 128-byte REU slot. Reclaimed IDs can be reused, so a long-lived ID alone does not guarantee the same vessel identity.
| A / action | Meaning |
|---|---|
1 | Lock the record and fetch its metadata and available payloads into the host scratch area. Carry clear means success; carry set means unavailable. This also selects the vessel field reader for BASIC’s USR delegate. |
0 | Persist consumed visual metadata and unlock the same record. This may promote its latest cell to the drawn cell and acknowledges the pending vessel-message notification. |
4 | Unlock without writing the scratch metadata back or changing the drawn cell. Use for a read-only inspection. It does not acknowledge a pending vessel-message notification. |
After a successful lock, BASIC reads fields through USR(field). Numeric results use the C128 floating-point accumulator; string results use BASIC string services. The $807A getter exposes the fetched scratch buffer, and $800E exposes the nine-byte registry scratch record. A native frontend can wrap the field reader or interpret the fetched data using the matching build’s layout.
Keep the record locked while copying the fields you need, then release it. The scratch space belongs to the runtime and is reused by the next transaction; it is not a permanent per-vessel buffer. Unlock with the same ID, and only after a successful lock.
For a guided example of the BASIC side, see Getting vessel data with USR, including field selectors and numeric/string results.
Lock, field-access and unlock implementation · Registry and record IDs · REU layout
CALLS IN / NOTIFICATIONS OUT
The runtime tells your application what changed.
The notification pointer returned by $8004 leads to a shared flag byte and its event context. BASIC uses WAIT to wait for selected flags. Another frontend can read the same state from its own event loop.
| Mask | Signal | Interpretation |
|---|---|---|
$01 | Wake-up | Let the foreground runtime and application make progress. |
$02 | Mouse | Published mouse notification; details are at offset +8. |
$04 | Tick | A toggling state: compare with the last handled tick, rather than clearing it as a pending event. |
$08 | Connection | Serial connection state. |
$10 | Data flow | Recent incoming activity; this is a state indicator. |
$20 | Vessel message | A published vessel update for the frontend. |
$40 | Status / error | A status, warning or error code; successful QR completion also uses this signal. |
$80 | Keypress | A key passed on by the foreground manager. |
| Offset | Contents |
|---|---|
+0 | Notification flags. |
+1 | Vessel action and AIS message type packed into one byte. |
+2, +3 | Vessel-message record ID, low then high. |
+4 | Keypress code. |
+5 | Status / error / warning code. |
+6, +7 | Context for that status code. For QR-ready code 14, the record ID low/high. |
+8 | Mouse details. |
+9 | Current frontend wait-mask mirror, also discoverable through $8045. |
These flags are a shared handshake, not an event queue. Repeated signals can coalesce. Read the relevant context before acknowledging an event, preserve unrelated bits, and coordinate acknowledgements with interrupt updates. Keep the wait-mask mirror consistent with the events your frontend can currently handle: the runtime uses it when deciding what to deliver.
IRQ supplies the bits needed to get BASIC going; once IGONE runs, foreground ML can publish further events. A native loop replaces that foreground scheduling role. It still needs to service the runtime and consume its notifications.
The WAIT tutorial explains masks and acknowledgement; the IGONE tutorial explains execution and wake-ups. The graphics frontend shows the complete event handling.
CURRENT EXPORTED ENTRIES
Find the service you need.
The tables retain the source names, including legacy names. Addresses describe the checked build. Detailed payload layouts and implementation helpers should always be taken from its source and symbols.
Runtime and lifecycle
| Address | Entry | Contract |
|---|---|---|
$8000 | nmi | Runtime initialization wrapper (legacy name, not a hardware NMI entry). Initializes decoder and storage state; use during startup, not as a service tick. |
$80BB | restartRuntimeIrqApi | Startup hook installation: resets serial work buffers, installs IRQ and IGONE, starts the clock and enables IRQs. See the integration notes before using outside BASIC. |
$80A1 | rearmRuntimeIrqTimerApi | Rearm only the runtime CIA1 timer after disk I/O. Preserves caller flags/MMU; does not reset serial buffers or reinstall hooks. |
$804E | resumeDecoder | Compatibility entry; currently returns immediately. It does not resume serial flow. |
$804F | startIdleCleanupApi | Request a bounded cleanup pass. Foreground servicing must continue for cleanup to progress. |
$8052 | getIdleCleanupStatusApi | Normal build: A bit 6 indicates active; X/Y = reclaimed count low/high. Debug builds also encode fault details; consult idleCleanup.asm. |
$8073 | setDataDrive | A = data drive, X = raw-logging flag. Stores those settings. |
Vessel access and scratch
| Address | Entry | Contract |
|---|---|---|
$8022 | getVesselData | A = action; X/Y = record ID low/high. See the transaction contract. |
$8035 | getVesselAtLocation | A = column, X = row. Returns Y = matching drawn, unlocked vessel count; A/X = first record ID low/high. Y = 0 means no match (A/X also zero); ID zero itself is valid. |
$800E | getVesselBufferAddressPointer | Returns A/X pointing to the nine-byte Bank 0 registry scratch record for BASIC access. This is not the canonical Bank 1 registry. |
$8013 | getVesselRegistryMemoryLocationPointer | Returns A/X pointing to the 16-bit bufferTail high-water mark (next unused ID), despite the legacy name. It is not the registry base address or a live vessel count. |
$807A | getBASICScratchPointer | Returns A/X pointing to the fetched metadata and payload staging area. Read within a successful vessel lock. |
Notifications and pointer state
| Address | Entry | Contract |
|---|---|---|
$8004 | getNotifyFlagPointer | Returns A/X pointing to the notification block. |
$8045 | getBasicWaitMaskPointer | Returns A/X pointing to the one-byte frontend wait-mask mirror. |
$807F | getMouseQueryPointer | Returns A/X pointing to the current query column byte, followed by the row byte. |
Runtime counters
| Address | Entry | Contract |
|---|---|---|
$8009 | getTotalVesselsTrackedCounterPointer | Returns A/X pointing to a 16-bit remaining-slot count, despite the name. Tracked vessels = configured capacity minus this value (capacity is currently 1,000). |
$8018 | getTotalMessagesReceivedCounterPointer | Returns A/X pointing to the 32-bit total-message counter. |
$801D | getTotalFailedUpdatesCounterPointer | Returns A/X pointing to the 16-bit failed-update counter. |
$8084 | getCRCFailuresCounterPointer | Returns A/X pointing to the 16-bit NMEA checksum-failure counter. |
$8089 | getMessageTypeCountersPointer | Returns A/X pointing to nine 16-bit counters, ordered by AIS type: 1, 2, 3, 4, 5, 9, 18, 21, 24. |
$808E | getSkippedNotificationsCounterPointer | Returns A/X pointing to the 16-bit skipped-notification counter. |
$80F5 | getRawDataDropCounter | Returns A/X pointing to the one-byte raw-log drop counter. |
Filtering and chart extent
| Address | Entry | Contract |
|---|---|---|
$8069 | setAISMaskBlock | Returns A/X with the address of a second routine. Call that returned routine with A = mask and X = block 0–7. Block 0 represents AIS types 1–8; block 1 represents 9–16, and so on. |
$806E | listenToAISMessageType | Returns A/X with the address of a second routine. Call it with A = AIS type 1–63, X = 0 to disable or nonzero to enable. That routine returns carry set for a valid type, clear otherwise. |
$8038 | setChartExtend | Selects the BASIC USR extent setter. Follow with USR(north), USR(east), USR(south), USR(west), in that order. |
$8057 | getChartExtend | Selects a USR getter whose current implementation returns zero. It does not currently return the chart bounds. |
BASIC and viewer helpers
| Address | Entry | Contract |
|---|---|---|
$8025 | getTimeString | Refreshes the time snapshot and selects Time.getDateTimeBasic as the USR delegate. The call itself does not return an A/X string pointer. |
$8093 | setDelegateUSR | Installs the runtime USR trampoline in BASIC's USR vector. It does not select a new field operation by itself. |
$804A | requestCurrentCellQueryApi | Starts the graphics pointer-cell vessel query / QR path. Successful QR completion arrives asynchronously as status code 14 on the status/error flag. |
$8064 | getWindowRoutine | Returns A/X with the callable address of windowPreparePrintAtRowLine, the text-window cursor restoration helper. |
$809E | enableBasic8MouseBridge | A = 0 holds/disables the bridge; A = 1 releases a hold and enables when holds reach zero; A = 2 clears holds and resynchronizes from the BASIC 8 pointer. |
$80BE | copyBackgroundToBank1 | Copies the locked vessel's saved REU background into the fixed BASIC 8 Bank 1 stash. |
$80D2 | copyBackgroundFromBank1 | Copies the fixed BASIC 8 Bank 1 stash into the locked vessel's saved REU background. |
$80E6 | clearOwnerMatrixApi | Clears the Bank 1 display-cell ownership matrix. |
$80E9 | claimOwnerCellApi | Uses the locked record ID; X = column, Y = row. Returns A = 0 for success, 1 for another owner, 2 for an invalid cell. |
$80EC | releaseOwnerCellApi | Uses the locked record ID; X = column, Y = row. Returns A = 0 for success, 1 if not the owner, 2 for an invalid cell. |
$80EF | startScreenRefreshApi | A = display-class mask (bits 0–4: Class A, Class B, base station, aid to navigation, SAR). Clears visual state and starts staged notifications; carry set means active. |
$80F2 | getScreenRefreshStatusApi | Advances the refresh pump and returns carry set while active, clear when done. This status call performs work. |
Multi-byte counters listed here are little-endian. Take a consistent snapshot before converting or displaying them; their bytes are shared runtime state. Flags and field/string representations have their own contracts.
Implementations: API wrappers and viewer helpers · cleanup status · chart extent handlers · mouse bridge modes
A FRONTEND OF YOUR OWN
BASIC is one caller. Yours can be another.
An application written in another language can provide its own foreground loop: service the AIS runtime, inspect notifications, update its interface, and repeat. IRQ and NMI handlers still handle their interrupt work. The integration needs a bridge that supplies registers, captures results and manages memory mapping around calls.
- Load a matched runtime. Use the module set and placement in the current build and loader. The disk separates serial code, bridges, registry, decoder/API, runtime support, AIS logic, file operations and REU support. Loading
DECODERalone does not provide the complete engine. - Respect the memory contract. Reserve the Bank 1 registry and hash, REU slots, shared scratch, low-memory bridges and zero-page/FAC areas. Use the existing banking and REU helpers; REU DMA must go through
reu.runDma, which forces 1 MHz for the transfer. - Supply the execution schedule. Initialize in the expected environment, arrange IRQ/NMI and foreground servicing, and select the events your frontend will consume. Do not install the BASIC IGONE hook and assume it will run in a native-only loop.
- Choose your field-access bridge. Register calls are straightforward to wrap. USR/FAC/string helpers still need their C128 ROM and BASIC workspace contracts, even when the application itself is written in another language. A native typed-field interface would be an additional adapter.
The same division of responsibilities can guide a future implementation on another platform. Reusing these particular binaries, however, requires the C128 execution environment or emulation of it.
Keep the implementation beside the reference
- README.md — architecture, API overview, current build and memory map.
- Basic8Config.asm — exported entries, initialization and viewer helpers.
- basicinterface.asm — vessel transactions and BASIC field conversion.
- ais4cbmManager.asm — foreground work, notifications and message masks.
- buildBASIC8.asm — modules packaged in the current disk.
GitLab links follow main. For an integration, keep a specific source revision and its generated symbols alongside the binaries you use.