PLOT codes — VDU 25 k reference

PLOT k, x, y (BASIC) ≡ VDU 25, k, xlo, xhi, ylo, yhi. k selects one of 256 graphics primitives operating on the current graphics cursor + the point (x, y).

The User Guide (1982) documented k = 0-95; the Master extends with k = 96-255 for the new rectangle / parallelogram / circle / ellipse / flood-fill primitives. Codes routed to unknown PLOT codes are dispatched via VDUV (&226) — see calls.

Structure of k — the precise semantics

Per master-rm Ch E.3, when a PLOT command is parsed the parameters are transformed and the low 3 bits of k select coordinate-handling + colour mode independently:

k mod 8 — coordinate mode and colour selection

k mod 8CoordinateColour / plot mode
0Relative to last visited point”terminate rapidly”: move old cursor → current, current → (x,y) — no plot (for fills/unknown codes: colour undefined, plot mode 5 “leave unchanged”)
1RelativeLast-set foreground colour + its plot mode (last VDU 18 with colour < 128)
2RelativeColour undefined; plot mode 4 (invert) — i.e. “draw in inverse”
3RelativeLast-set background colour + its plot mode (last VDU 18 with colour ≥ 128)
4Absolute(same as 0: terminate rapidly)
5AbsoluteLast foreground
6AbsoluteInvert
7AbsoluteLast background

This is why PLOT 4, x, y = MOVE (move-absolute, no plot) and PLOT 5, x, y = DRAW (draw-absolute in foreground colour). After every plot, the old cursor moves to where the current cursor was, then the current cursor moves to (x,y) — this 2-cursor history is what enables 2-point primitives (triangles, rectangles, parallelograms) and 3-point primitives (arcs, sectors).

k DIV 8 — operation group

k DIV 8k rangeOperation
00-7Solid line (both endpoints included)
18-15Solid line (last endpoint omitted — for repeat-XOR cleanup)
216-23Dotted line (both endpoints; pattern restarted)
324-31Dotted line (last endpoint omitted; pattern restarted)
432-39Solid line (initial point omitted)
540-47Solid line (both endpoints omitted)
648-55Dotted line (initial omitted; pattern continued from previous)
756-63Dotted line (both omitted; pattern continued)
864-71Single point at (x,y)
972-79Horizontal line fill L+R until non-background
1080-87Filled triangle (old cursor, current cursor, (x,y))
1188-95Horizontal line fill R only until background
1296-103Filled rectangle (current cursor + (x,y) as opposite corners) — Master
13104-111Horizontal line fill L+R until foreground — Master
14112-119Filled parallelogram — Master
15120-127Horizontal line fill R until non-foreground — Master
16128-135Flood fill to non-background — Master
17136-143Flood fill to foreground — Master
18144-151Circle outline (centre = current cursor; (x,y) on circumference) — Master
19152-159Filled circle — Master
20160-167Circular arc (centre = old cursor; current cursor = arc start; (x,y) direction defines arc end on circle; anticlockwise) — Master
21168-175Filled chord segment (arc + straight chord) — Master
22176-183Filled sector (pie slice: arc + 2 radii to centre) — Master
23184-191Move/copy rectangle (special k handling; see below) — Master
24192-199Ellipse outline — Master
25200-207Filled ellipse — Master
26208-231Reserved (routed to unknown PLOT vector)
27232-239Reserved for Acornsoft sprites
28-31240-255Application-reserved — routed through VDUV (&226/&227) with C=0, A=k

”Logical inverse” colour

When using k mod 8 = 2 or 6, the colour written is the logical-inverse of what’s already there: inverse(logical) = (num_colours - 1) - logical. So 0↔1 in 2-colour modes, 0↔3 / 1↔2 in 4-colour modes, 0↔15 / 1↔14 / etc. in 16-colour mode. This is the cheapest way to draw “XOR” overlays that cleanly remove themselves on a second draw — though for performance work you usually want VDU 18 EOR mode (GCOL 3) instead.

Move/copy rectangle (PLOT 184-191) — special k handling

Per master-rm Ch E.3 (this group breaks the usual k MOD 4 colour convention):

kOperation
184/185Move rectangle, relative
186/187Copy rectangle, relative
188/189Move rectangle, absolute
190/191Copy rectangle, absolute

Source rectangle = opposite corners at old + current graphics cursors. Destination = bottom-left at (x,y). Move clears the source area outside the destination to background; copy leaves the source alone. Any source pixels outside the graphics window are treated as background.

This is the BBC’s “bit-blit” — useful for sprite-stamping, scrolling sub-regions, and double-buffered animation when combined with shadow RAM.

Geometric primitives — precise definitions

These are the Master / GXR-era primitives, exactly as specified in master-rm Ch E.3. The wiki used to attribute them to Master ARM Ch 6 + App 2 — that’s where Acorn’s older docs are; the MRM is the canonical user-level spec.

Circles (144-151 outline, 152-159 filled)

  • Centre = current graphics cursor position
  • Point (x,y) = a point on the circumference
  • Radius = distance from centre to (x,y)

Filled circle is “exactly bounded” by its outline: every pixel that would be lit by the outline is included, every pixel inside is included, no pixel outside.

⚠ Radius limit: in some modes very-large radii suffer numerical errors; reliable up to radius < 16384 external units.

Arcs / chord segments / sectors (160-183)

These are 3-point primitives — they use old cursor + current cursor + (x,y) to define a partial circle:

  • Centre = old graphics cursor
  • First arc endpoint = current graphics cursor
  • Second arc endpoint direction = (x,y) direction from centre (the line from centre through (x,y) intersects the circle at the second endpoint)
  • Arc goes anticlockwise from first to second endpoint
CodesPrimitiveWhat’s filled
160-167Arc (outline only)Just the curve
168-175Filled chord segmentArea bounded by the arc + straight chord between endpoints
176-183Filled sectorPie slice: area bounded by the arc + 2 radii to centre

Useful pattern: set graphics cursor to centre, MOVE to first arc point, PLOT to define second arc direction.

Ellipses (192-199 outline, 200-207 filled)

Axis-aligned only (natively). Per Ch E.3 definition:

  • Centre = old graphics cursor
  • X coordinate of current cursor = X coordinate of one of the two horizontal intercepts (i.e. ellipse half-width along centre’s horizontal axis)
  • (x,y) point = the highest point of the ellipse if it lies above centre, otherwise the lowest point — the Y coordinate of the current cursor is ignored

So you supply: centre, half-width-X, half-height-Y. ⚠ Numerical inaccuracy starts to bite for half-height > ~2500 external units.

For rotated ellipses, the MRM gives a worked BASIC PROC (Ch E.3 p202) using cos/sin shear:

DEFPROCellipse(cx%, cy%, semimajor, semiminor, angle, type%)
LOCAL cosang, sinang, maxy, shearx, slicewidth
cosang  = COS(RAD(angle))
sinang  = SIN(RAD(angle))
maxy    = SQR((semiminor * cosang)^2 + (semimajor * sinang)^2)
shearx  = (semimajor^2 - semiminor^2) * cosang * sinang / maxy
slicewidth = semimajor * semiminor / maxy
MOVE cx%, cy%
MOVE cx% + slicewidth, cy%
IF type% = 0 THEN plotcode% = 197 ELSE plotcode% = 205
PLOT plotcode%, cx% + shearx, cy% + maxy
ENDPROC

type% = 0 → outline (PLOT 197 = ellipse-outline, absolute, foreground); type% != 0 → solid (PLOT 205 = solid-ellipse, absolute, foreground).

Flood fill (128-143)

  • 128-135: flood from (x,y) until non-background pixels found
  • 136-143: flood from (x,y) until foreground pixels found
  • Flood spreads orthogonally (NESW), not diagonally
  • Will abandon if: area too complex, fill colour can itself be filled (e.g. PLOT 131/135 with VDU 18 mode 0 background-leave), or ESCAPE pressed
  • Uses &8400-&87FF in MOS sideways RAM as workspace (Master second 32 KB)

Horizontal line fill (72-79, 88-95, 104-111, 120-127)

Four variants, each scanning horizontally from (x,y) until a boundary pixel:

CodesDirectionStops at
72-79Left + rightnon-background
88-95Right onlybackground
104-111Left + rightnon-foreground (foreground colour) — Master
120-127Right onlyforeground — Master

After fill, old/current cursors are set to L/R endpoints of the line filled. Readable via OSWORD A=13 — useful for polygon-scanline fills where you query the fill extent and then move on.

If the seed pixel is the wrong colour, the cursors are set to indicate an error state (cursors not on the same horizontal line, or current = (x,y) + old one pixel left).

Triangle (80-87)

Fills triangle with vertices at old cursor, current cursor, (x,y). “Exactly bounded” — pixels on the boundary lines are included; nothing outside.

Parallelogram (112-119)

Vertices in cyclic order: old cursor, current cursor, (x,y), 4th-point. 4th-point is calculated in internal pixel coords (not external) to guarantee parallel sides: 4th = (x,y) − current + old.

Common patterns

Move-then-line

MOVE 100, 100         : REM PLOT 4, 100, 100
DRAW 500, 300         : REM PLOT 5, 500, 300

The two-step move + draw pattern. Equivalent to PLOT 4, ... then PLOT 5, ....

XOR-sprite via dotted-line cleanup

GCOL 3, 1             : REM EOR mode, foreground
PLOT 5, X, Y          : REM draw line foreground (XOR onto screen)
REM ... timing / movement ...
PLOT 13, X, Y         : REM PLOT 5 but with last point omitted

The “omit last point” variants (k = 8-15, 24-31) avoid leaving a stray pixel when XOR-drawing back over a previously-drawn line.

Filled triangle

MOVE 100, 100         : REM PLOT 4 — first vertex
MOVE 500, 100         : REM PLOT 4 — second vertex (replaces graphics cursor)
PLOT 85, 300, 400     : REM filled triangle (last 2 visited + this)

This is the canonical primitive for textured / filled polygons before Master added native rectangle/circle support.

Single point

PLOT 69, 500, 500     : REM point at absolute (500, 500) in foreground
PLOT 71, 500, 500     : REM same point in background (erase)
PLOT 70, 500, 500     : REM same point in inverse colour (toggle)

PLOT 70 is the classic single-pixel XOR — repeating it removes the pixel cleanly.

Performance notes

  • PLOT primitives go through the MOS VDU driver and (from BASIC) the interpreter dispatch overhead. From BASIC, PLOT 69 takes hundreds of microseconds per point on a 2 MHz Model B — too slow for real-time animation of more than a few hundred points per frame (measure on your target machine if precise figures matter; the cost varies by mode, pixel position, and clipping window).
  • For per-frame sprite work, bypass the VDU driver and write screen RAM directly. See fast-animation for the byte-move sprite pattern, custom-modes for direct-CRTC bypass.
  • The “horizontal line fill until colour” primitives (PLOT 72-79, 88-95) are surprisingly cheap — useful for filling complex polygon scanlines without writing your own fill routine. Worth measuring against a hand-coded scanline filler if you’re doing 2D polygon work.
  • Flood-fill (PLOT 128-143, Master only) is slow — recursive in classic implementations. Avoid in tight loops; pre-render fills into an off-screen buffer if needed.

Cross-references

  • vdu — VDU 25 invocation; VDU 24 sets the graphics clipping window for PLOT.
  • video-ula — palette (logical colour) mechanics; logical-inverse computation.
  • modes — coordinate range per mode (external coords 0-1279 × 0-1023; internal pixel ranges differ).
  • fast-animation — when to leave PLOT for direct byte moves.
  • custom-modes — when to leave the VDU driver entirely.
  • bbc-user-guide Ch 33 (BASIC PLOT keyword) + Ch 29 (Advanced Graphics).
  • master-arm Ch 6 + App 2 — Master-era PLOT extensions.

This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.