← Developers

Developers · Interface specification

Linear RAM Text Overlay Interface

1Overview

This document specifies a text layer for an Apple II video card. An Apple II program puts 16-bit text cells in one linear block of main or auxiliary RAM. The card watches writes to that block, keeps a private copy, and draws the text above the normal Apple picture.

The card only watches RAM writes. It does not change Apple RAM, drive the data bus for RAM cycles, stop the processor, or change Apple video soft switches.

Conventions

The interface occupies the 16-byte DEVSEL block of one slot: $C080 + s×$10 for slot s. The slot is implementation-defined; a card may use any slot whose DEVSEL block it owns. This document writes the bytes as offsets +$0 through +$F, and software finds the slot with the scan in §9. Multi-byte values use little-endian order. Bit 0 is the low bit.

The interface asserts no IRQ, NMI, DMA, or RDY signal. It drives the Apple data bus only for reads from its own DEVSEL block.

2DEVSEL register reference

OffsetNameR/WFunction
+$0INDEXRWSelects one indirect register, 0–255.
+$1DATARWReads or writes the selected indirect register. INDEX does not change.
+$2DATA_INCRWReads or writes the selected indirect register, then increments INDEX modulo 256.
+$3CMDWIssues one command from the table below.
+$4STATUSRCurrent capture and display state.
+$5–+$7RESERVEDRRead as 0. Writes have no effect.
+$8–+$DMAGICRASCII LINTXT: $4C $49 $4E $54 $58 $54.
+$ESIGRAlways $4C (’L’).
+$FVERRInterface version in BCD; $10 means 1.0.

Commands

ValueNameAction
$00OFFHide the layer at the next output frame and stop buffer capture.
$01ARMCheck and latch the staged buffer settings, clear the card’s new shadow buffer, and start capture. The shown layer does not change.
$02SHOWAt the next output frame, make the armed buffer active and show it.
$03HIDEHide the layer at the next output frame but keep capture armed.

Other command values are reserved and have no effect. The card ignores every CMD write while BUSY is 1. An accepted ARM sets BUSY during the CMD write cycle itself. An accepted SHOW, HIDE, or OFF sets FRAME_PENDING during the CMD write cycle itself. The first STATUS read after a CMD write therefore shows the command’s effect; software never sees an accepted command that is still invisible in STATUS.

STATUS bits

BitNameMeaning when set
7BUSYThe card is clearing or changing an internal shadow buffer.
6STALEThe card lost one or more watched writes. It has hidden the layer. Software must ARM and refill it.
5CONFIG_ERRORThe last ARM request failed a range or value check.
4FRAME_PENDINGA SHOW, HIDE, or OFF change waits for the next output frame.
3–2RESERVEDAlways 0 in version 1.0.
1ARMEDThe card watches writes to the latched buffer range.
0VISIBLEThe layer is part of the current output frame.

3Indirect register reference

Software selects a byte with INDEX and reads or writes it through DATA. DATA_INC makes a run of registers cheaper to set. Unknown indirect registers read as 0 and ignore writes.

IndexNameR/WFunction
$00BASE_LORWStaged Apple RAM buffer address bits 7:0.
$01BASE_HIRWStaged Apple RAM buffer address bits 15:8.
$02CONFIGRWStaged bank, character mode, font, blink, and background settings; see §4.
$03COLSRWStaged column count, 1–255. Zero is invalid.
$04ROWSRWStaged row count, 1–127. Zero is invalid.
$05ORIGIN_X_LORWStaged left edge in output pixels, bits 7:0.
$06ORIGIN_X_HIRWStaged left edge in output pixels, bits 15:8.
$07ORIGIN_Y_LORWStaged top edge in output pixels, bits 7:0.
$08ORIGIN_Y_HIRWStaged top edge in output pixels, bits 15:8.
$09SCALERWVertical scale in bits 7:4 and horizontal scale in bits 3:0. Each is 1–15; zero is invalid.
$0ACURSOR_XRWLive cursor column, 0–255.
$0BCURSOR_YRWLive cursor row, 0–127.
$0CCURSOR_CTRLRWLive cursor enable, blink, and shape; see §7.
$0DFILL_CHARRWCharacter value used when ARM clears the new shadow buffer.
$0EFILL_ATTRRWAttribute value used when ARM clears the new shadow buffer.
$0FRESERVEDRReads as 0.
$10CANVAS_W_LORCurrent output canvas width in pixels, bits 7:0.
$11CANVAS_W_HIRCurrent output canvas width in pixels, bits 15:8.
$12CANVAS_H_LORCurrent output canvas height in pixels, bits 7:0.
$13CANVAS_H_HIRCurrent output canvas height in pixels, bits 15:8.
$14ACTIVE_BASE_LORShown buffer base, bits 7:0.
$15ACTIVE_BASE_HIRShown buffer base, bits 15:8.
$16ACTIVE_CONFIGRShown buffer CONFIG value.
$17ACTIVE_COLSRShown buffer column count.
$18ACTIVE_ROWSRShown buffer row count.
$19–$1DACTIVE_LAYOUTRShown ORIGIN_X low/high, ORIGIN_Y low/high, and SCALE, in that order.
$1ECAPSRVersion 1.0 returns $7F; bits 6:0 report staged handoff, cursor, transparent background, 8×16, 8×14, CP437, and VT100 support.
$1F–$FFRESERVEDRRead as 0. Writes have no effect.

Writes to staged registers never change the shown layer or the watched RAM range. ARM latches all staged registers except the live cursor registers. SHOW later copies the armed layout to the active layout at one output-frame edge.

4Cell and character formats

Cells use two bytes with no row padding:

cell = row * COLS + column
address = BASE + 2 * cell

address + 0   character
address + 1   attribute

A 16-bit little-endian store would hold attribute×$100 + character. An 80×24 buffer uses 3,840 bytes. An 80×25 buffer uses 4,000 bytes.

CONFIG bits

BitName01
0AUX_BANKMain RAMAuxiliary RAM
1CP437VT100 character modeCP437 character mode
2FONT_168×14 font8×16 font
3BLINK_MODEAttribute bit 7 is background bit 3Attribute bit 7 enables blink
4TRANSPARENTDraw the cell background colorLet Apple video show through clear glyph pixels
7–5RESERVEDWrite 0; reads return 0.

VT100 character mode

The low seven character bits select a glyph. Character bit 7 requests underline. The card draws underline across all eight logical pixels on font row FONT_H−2; the last font row remains clear.

Stored codeGlyph
$00–$1FDEC Special Graphics codes $5F–$7E, in order.
$20–$7EUS ASCII.
$7FBlank.

A program sets underline by ORing $80 into any stored code. This includes a space, so an underlined space still draws a line. A terminal parser maps bold to a bright foreground color and resolves reverse video by swapping foreground and background before it stores the cell.

CP437 character mode

All eight character bits select one of the 256 fixed CP437 glyphs. No character bit remains for underline. This mode suits ANSI and BBS screens that need the full IBM graphic set.

5Apple RAM capture

ARM forms one watched range from BASE, CONFIG.AUX_BANK, COLS, and ROWS. The byte count is 2×COLS×ROWS. A valid range meets every rule below:

BASE need not be aligned. The $0200 floor keeps zero page and the stack outside the watched range; those pages follow ALTZP and are always in use. The floor also makes an ARM with the reset defaults fail, because BASE resets to 0. Version 1.0 does not accept slot space, ROM, or language-card RAM. Software owns the chosen range and must not also use it for code, disk work space, or other changing data.

The card watches the actual target bank of each Apple bus write. It must account for AUXWRITE, 80STORE, PAGE2, and any other bank-routing state. Writes from a DMA bus master count in the same way as processor writes. On a machine with no auxiliary RAM, a buffer with AUX_BANK = 1 never receives writes; ARM cannot detect this, and the layer then shows only fill cells.

If the buffer overlaps native text or graphics memory, writes can change both the normal Apple picture and the overlay. Use unused RAM when the two pictures must stay independent.

ARM

  1. The card sets BUSY during the ARM write cycle itself.
  2. The card checks all staged values. If any check fails, it sets CONFIG_ERROR, clears BUSY, and changes no active or armed state.
  3. The card latches the staged values as the new capture settings.
  4. The card fills its private shadow cells with FILL_CHAR and FILL_ATTR. It does not write those values to Apple RAM.
  5. While the fill runs, BUSY stays 1 and watched writes need not be kept.
  6. When the fill ends, the card clears BUSY and sets ARMED. From then on it keeps every write in the range.

Software polls STATUS until BUSY is 0, then checks CONFIG_ERROR, then writes every cell that SHOW should display. The card cannot read old bytes that were already in Apple RAM before ARM.

Lost writes

If the card cannot keep a watched write after ARMED becomes 1, it sets STALE, clears ARMED, and hides the layer at the next output frame. It must not keep showing data that it knows may be wrong. A new valid ARM clears STALE and CONFIG_ERROR.

6Handoff and frame rules

SHOW changes the active base, bank, grid, origin, scale, format, and font together at one output-frame edge. It never exposes a mix of old and new settings. SHOW has no effect unless ARMED is 1, BUSY is 0, and STALE and CONFIG_ERROR are both 0. SHOW does not clear ARMED: after the flip, the armed range is the active range, and capture continues on it. A later HIDE then SHOW shows the same buffer again without a new ARM.

After SHOW, later watched writes update the shown layer in bus order. A card may delay those writes before they reach its output, but it must preserve their order. The exact delay is implementation-defined and should not exceed two output frames under normal load.

HIDE and OFF also take effect at an output-frame edge. HIDE leaves capture armed. OFF stops capture after it hides the layer.

Double buffering

Software may keep one buffer visible while it prepares another:

  1. Leave buffer A visible.
  2. Stage buffer B, issue ARM, and wait for BUSY to clear.
  3. Do not change buffer A after ARM; the card now watches buffer B.
  4. Fill buffer B and issue SHOW.
  5. The card switches from A to B at one output-frame edge.

Use non-overlapping ranges for A and B. ARM may clear the card’s private copy of the new range, so an overlap could change cells that the old active layout still reads.

To know when the flip is complete, poll FRAME_PENDING until it is 0. Do not poll VISIBLE for this; VISIBLE is already 1 while the card flips from A to B.

7Color, background, blink, and cursor

The attribute byte follows VGA order:

bits 3:0   foreground color, 0-15
bits 6:4   background color bits 2:0
bit 7      blink when CONFIG.BLINK_MODE = 1
           background color bit 3 when CONFIG.BLINK_MODE = 0

In blink mode, the background range is 0–7. Outside blink mode, the background range is 0–15 and cells do not blink. All blinking cells share one phase: visible for 500 ms, hidden for 500 ms. On the hidden phase, glyph and underline pixels become background pixels, or clear pixels when CONFIG.TRANSPARENT is 1.

The interface does not observe $C022. Software writes VGA-order color values straight into each attribute byte.

When TRANSPARENT is 0, clear glyph pixels use the cell background color. When TRANSPARENT is 1, clear glyph pixels show the normal Apple picture below the layer. This setting applies to the whole buffer; version 1.0 has no per-cell transparency bit, so an ANSI cell with equal foreground and background stays a solid color block.

Fixed palette

Index0123456789ABCDEF
Color
RGB 00000A0A00AA A00A0AA50AAA 55555F5F55FF F55F5FFF5FFF

This is the standard VGA/ANSI color order. It differs from the IIgs $C022 color order used by Text Overlay Card Interface 1.0.

Cursor

CURSOR_CTRL bitNameMeaning
0ENABLEDraw the cursor when its row and column are inside the active grid.
1BLINKBlink the cursor on the same 500 ms phase as blinking cells.
3–2SHAPE0 block, 1 underline, 2 left bar, 3 reserved.
7–4RESERVEDWrite 0; reads return 0.

A block cursor reverses foreground and background selection for the cell. An underline cursor forces the logical row FONT_H−2 to foreground. A left-bar cursor forces logical column 0 to foreground. Cursor changes take effect at the next output frame and do not change Apple RAM or the card’s text shadow.

8Canvas, placement, and clipping

The canvas is the card’s full active Apple picture, not its border or bezel. Pixel (0,0) is its top-left corner. CANVAS_W and CANVAS_H report its current size. The reference implementation reports 1120×768 for legacy Apple modes and 1280×800 for SHR. Card-owned menus and status marks may draw above this text layer.

The unscaled cell size is 8×14 or 8×16 pixels. SCALE sets an integer size on each axis. The card places cell (x,y) at:

left = ORIGIN_X + x * 8 * horizontal_scale
top  = ORIGIN_Y + y * FONT_H * vertical_scale

The card clips any part outside the canvas. An origin beyond the right or bottom edge is valid but shows no cells. The card does not resize the grid when the output mode changes; software can read the new canvas size and ARM a new layout.

An 80×24 VT100 grid with the 8×14 font and SCALE $22 is 1280×672 pixels. On the reference SHR canvas it sits at (0,64). On the 1120×768 legacy canvas, SCALE $21 makes the same grid 640×672 pixels and centers it at (240,48). Software should read CANVAS_W and CANVAS_H before it chooses these values.

9Reset and card detection

Reset state

After card or Apple reset: the layer is hidden and not armed; INDEX, BASE, and origin are 0; CONFIG is $08 (main RAM, VT100, 8×14, blink mode, opaque background); COLS is 80; ROWS is 24; SCALE is $11; FILL_CHAR is $20; FILL_ATTR is $07; and the cursor is off at (0,0). STATUS is 0. BASE resets below the $0200 floor, so an ARM fails with CONFIG_ERROR until software stages a real address.

Detection

The interface adds no slot-ROM or $C800 decode; it does not disturb the ROM its host slot already carries. Every identification byte sits at a fixed DEVSEL offset, in the manner of Apple’s fixed-address auxiliary-memory entry points: identification must never depend on card state such as INDEX. Detection uses reads only. A detector scans the candidate slots; in each slot it reads SIG and VER first, and only if those match does it read the six MAGIC bytes.

Etiquette is part of the protocol. A probe must never write to a slot that has not passed every check — not even to INDEX. On some cards — a Disk II controller above all — every DEVSEL access has side effects; the two reads of +$E and +$F are the least intrusive probe possible.

; Entry: X = slot * $10.  PTR = any free zero-page pair.
; Exit: carry clear = interface found in that slot.
DETECT   LDA   $C08E,X
         CMP   #$4C             ; SIG = 'L'
         BNE   NOCARD
         LDA   $C08F,X
         CMP   #$10             ; version 1.0 or later
         BCC   NOCARD
         TXA
         ORA   #$88             ; DEVSEL base + $8 = first MAGIC byte
         STA   PTR
         LDA   #$C0
         STA   PTR+1
         LDY   #0
MAGIC    LDA   (PTR),Y
         CMP   IDSTR,Y
         BNE   NOCARD
         INY
         CPY   #6
         BCC   MAGIC
         CLC
         RTS
NOCARD   SEC
         RTS
IDSTR    HEX   4C494E545854   ; "LINTXT", high bits clear

The ID string uses a HEX directive because Merlin’s double-quoted ASC sets the high bits, and the compare would then always fail.

The reference implementation exposes these registers only while its SmartPort interface owns slot 7. It must not answer at $C0F0–$C0FF while SuperSprite owns that slot.

10Programming example

This SHR example stages an 80×24 VT100 buffer in main RAM at $6000. It uses an 8×14 font at 2× scale and places the 1280×672 layer at (0,64), centered on a 1280×800 canvas. A program that can also run in legacy modes must read the canvas registers and use the legacy layout above. This example assumes the reference slot-7 binding, so the DEVSEL block sits at $C0F0.

         LDA   #$00
         STA   $C0F0            ; INDEX = BASE_LO

         LDA   #$00
         STA   $C0F2            ; BASE_LO = $00; INDEX increments
         LDA   #$60
         STA   $C0F2            ; BASE_HI = $60
         LDA   #$08
         STA   $C0F2            ; main, VT100, 8x14, blink, opaque
         LDA   #80
         STA   $C0F2            ; COLS
         LDA   #24
         STA   $C0F2            ; ROWS
         LDA   #$00
         STA   $C0F2            ; ORIGIN_X = 0
         LDA   #$00
         STA   $C0F2
         LDA   #$40
         STA   $C0F2            ; ORIGIN_Y = $0040 = 64
         LDA   #$00
         STA   $C0F2
         LDA   #$22
         STA   $C0F2            ; 2x horizontal, 2x vertical

         LDA   #$01
         STA   $C0F3            ; CMD: ARM; BUSY sets in this write cycle
WAIT     LDA   $C0F4
         BMI   WAIT             ; wait for BUSY = 0
         AND   #$20
         BNE   BADCFG

         JSR   FILL6000         ; write all 3,840 bytes after ARM

         LDA   #$02
         STA   $C0F3            ; CMD: SHOW at next output frame
SHOWN    LDA   $C0F4
         AND   #$10
          BNE   SHOWN            ; FRAME_PENDING clears at the frame edge
          RTS

Before it exits, a program must remove its layer and wait for the clean output frame to take effect. OFF hides the layer and disarms capture; it does not change the Apple RAM buffer.

         LDA   #$00
         STA   $C0F3            ; CMD: OFF
 CLEAN    LDA   $C0F4
          AND   #$10
          BNE   CLEAN            ; do not return while the layer is visible

To write cell (column,row), compute $6000 + 2×(row×80 + column), store the character first, then the attribute. Either byte order is valid. A frame can contain one old byte and one new byte if the two stores cross an output-frame edge; use the staged second buffer when a whole-screen change must be exact.

11Compliance

Normative: the 16-byte DEVSEL block and its register offsets, indirect register meanings, command and status rules, range checks, the two-byte row-major cell layout, CONFIG and attribute bits, VT100 and CP437 code maps, fixed palette, cursor shapes, reset state, capture of the resolved RAM bank, lost-write behavior, frame-edge handoff, and the MAGIC, SIG, and VER bytes at their fixed DEVSEL addresses.

Implementation-defined: the slot, and any co-residency rules inside it (the reference implementation shares slot 7 with SmartPort and yields to SuperSprite and the boot menu); output canvas size; write-to-output delay within the stated two-frame goal; and the drawing order of card-owned menus above the text layer. It is worth reiterating that the reference implementation places the block in slot 7's DEVSEL space, $C0F0–$C0FF, alongside its SmartPort interface, which uses only that slot's ROM and expansion-ROM decodes. The two operate at the same time: a SmartPort call and an overlay register access never contend. The block is exposed exactly when SmartPort is, and must not answer while SuperSprite owns the slot.

Reserved for future versions: CONFIG bits 7:5, CURSOR_CTRL bits 7:4 and shape 3, DEVSEL offsets +$5 through +$7, indirect registers $1F through $FF, command values above $03, and STATUS bits 3:2. A future font-upload extension would use this reserved space.

Linear RAM Text Overlay Interface 1.0 draft · 2026-08-11 · Reference implementation: Appletini One. Review before implementation. The source of this page is docs/linear_ram_text_overlay_interface_v1.0.html in the firmware repository; a demo that uses it is software/textoverlay.a65.