@@ 0,0 1,133 @@
+# Console Output
+## Parameters
+| Name | a.k.a. | Value | Purpose |
+| :--------: | :----: | :----------: | ------------------------------------------------------------------------ |
+| W | | 80 (typ.) | Maximum width in characters |
+| H | | 25 (typ.) | Maximum height in characters |
+| FONTHEIGHT | FH | 8 (typ.) | Font Height; maximum number of rasters per character row. |
+| FONTWIDTH | FW | 256 (typ.) | Font width in bytes; maximum number of bytes per raster row in the font. |
+| FBSIZE | | W \* H \* FH | Maximum number of bytes in the framebuffer |
+## Global State
+| Name | Type | Invariant | Purpose |
+| :---------: | :--: | :--------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| cursorX | i8 | 0 <= n < W | Current cursor column |
+| cursorY | i8 | 0 <= n < H | Current cursor row |
+| cursorOfs | i16 | (0 <= n < FBSIZE) /\\ (n = FH\*W\*cursorY + cursorX) | Current cursor offset in the raw framebuffer, pre-computed because 65816 lacks efficient multiply functionality |
+| offCounter | i8 | 0 <= n < 256 | Cursor Off Counter. If **zero**, the cursor remains visible to the user. If **non-zero**, the cursor will appear to disappear. This makes screen updates faster. |
+| cursorDrawn | bool | n in {true, false} | If true, the block cursor is visible to the user; otherwise, it has been removed from the display. |
+## Main API
+### EMIT
+ Turn cursor off.
+ Copy glyph from font to current screen address.
+ Advance cursor right by one.
+ Turn cursor on.
+### TYPE
+ Turn cursor off.
+ For each character in the supplied buffer do
+ Copy glyph from font to current screen address.
+ Advance cursor right by one.
+ Turn cursor off.
+### TYPEZ
+ Turn cursor off.
+ While a NUL character has not been found, do
+ Copy glyph from font to current screen address.
+ Advance cursor right by one.
+ Retrieve next character.
+ Turn cursor on.
+### PAGE
+ Turn cursor off.
+ Set cursor to (0,0).
+ Fill framebuffer with 0.
+ Turn cursor on.
+### AT-XY
+ Turn cursor off.
+ Set cursor X,Y to coordinates given.
+ Turn cursor on.
+### AT-XY?
+ Return cursorX, cursorY, cursorOfs.
+### Turn Cursor On
+ Decrement offCounter if offCounter > 0.
+ If offCounter == 0 then Draw cursor.
+### Turn Cursor Off
+ Increment offCounter if limit not reached.
+ If offCounter != 0 then Erase cursor.
+## Miscellaneous Backing Functions
+These functions are not typically invoked by the client software, but rather exist primarily to support the main entry-points above.
+### Advance cursor right
+ If cursor already on right-hand edge of the display, then
+ Advance cursor down.
+ Return cursor to left edge of the display.
+ Else
+ Move cursor right by 1.
+ end
+### Advance cursor down
+ If cursor already on bottom edge of the display, then
+ Scroll display up.
+ Else,
+ Move cursor down by 1.
+ end.
+### Draw Cursor
+ If cursor not drawn then
+ XOR cursor rectangle.
+ Set cursor to drawn.
+ end
+### Erase Cursor
+ If cursor not erased then
+ XOR cursor rectangle.
+ Set cursor to erased.
+ End
+## Frame Buffer Procedures
+These functions are not typically invoked by the client software, but rather exist primarily to support the main entry-points above.
+### Copy Glyph to Current Screen Address
+This algorithm assumes a font is just a 2048x8 bitmap, arranged 256 bytes across and 8 bytes high.
+
+ Calculate font pointer from glyph.
+ Calculate framebuffer pointer by adding cursorOfs to the base address of the frame buffer.
+ For all raster rows in the font,
+ Copy byte from font to the framebuffer.
+ Advance font pointer by FW.
+ Advance framebuffer pointer by W.
+### XOR Cursor Rectangle
+ For each byte in the cursor image,
+ Invert all bits in the byte.
+### Scroll Display Up
+ Block copy the frame buffer from raster row FH to raster row 0.
+ Fill the bottom-most line with 0.
+### Fill Framebuffer
+ For each byte address in the frame buffer,
+ Set byte to desired value.
+### Fill Bottom-Most Line
+ For each byte address in the bottom-most line,
+ Set byte to desired value.
+## Cursor Coordinate Accessors
+Because of the need to keep the cursorX, cursorY, and cursorOffset variables in sync at all times, attempts to *alter* one or more of these variables should always be done behind a procedure of some kind. (It is always safe to read these variables when needed.) This should help keep text display relatively quick for a 4MHz CPU without multiplication instructions.
+### Set cursor X,Y
+This seemingly strange implementation exists because the 65816 lacks a multiply instruction. Therefore, when calculating `cursorOffset`, we just add or subtract a fixed quantity as often as needed to achieve the desired multiplication effect.
+
+This implementation is **slow** in the worst-case. I estimate about 32 clock cycles for each (for lack of better term) "row seek." Worst case on a 640x480 display with an 8-pixel font height, that's about 60\*32=1920 clock cycles (480us at 4MHz) to recalculate the new cursorOffset. But, since most cursor moves are expected to be only a small handful of rows at most, *on average*, performance should be faster than when using a dedicated multiplication routine.
+
+However, it is correct. As long as `cursorX` and `cursorY` are inside the character matrix boundaries, the new coordinates (remember, they are constrained as well) ensures that the new value of `cursorOffset` also resides within the framebuffer boundaries.
+
+ Constrain new cursor position.
+ Let delta be the (signed) difference from the new column to the current column.
+ Add delta to cursorX and to cursorOffset.
+ While new row lies below the current row, do
+ Move cursor down by 1.
+ end
+ While new row lies above the current row, do
+ Move cursor up by 1.
+ end
+### Constrain Cursor Position
+ If X is less than zero, then set X to 0.
+ If X is greater than or equal to W, then set X to W-1.
+ If Y is less than zero, then set Y to 0.
+ If Y is greater than or equal to H, then set Y to H-1.
+### Move Cursor Down by 1
+ Increment cursorY by 1.
+ Increment cursorOffset by W*FH.
+### Move Cursor Up by 1
+ Decrement cursorY by 1.
+ Decrement cursorOffset by W*FH.
+### Move Cursor Right by 1
+ Increment cursorX by 1.
+ Increment cursorOffset by 1.