{"id":1103,"date":"2026-08-08T17:23:52","date_gmt":"2026-08-08T17:23:52","guid":{"rendered":"https:\/\/tlindner.macmess.org\/?p=1103"},"modified":"2026-08-08T17:23:52","modified_gmt":"2026-08-08T17:23:52","slug":"one-true-sprite","status":"publish","type":"post","link":"https:\/\/tlindner.macmess.org\/?p=1103","title":{"rendered":"One True Sprite"},"content":{"rendered":"<h1>GIME Hardware Cursor Extension Proposal<\/h1>\n<h2>Overview<\/h2>\n<p>The GIME video system can provide a hardware mouse cursor by adding a small independent cursor overlay. The cursor is not stored in normal CoCo memory and does not require software to modify the display memory.<\/p>\n<p>The cursor is drawn after the normal video image is generated. Software only needs to provide:<\/p>\n<ul>\n<li>A cursor shape.<\/li>\n<li>A cursor position.<\/li>\n<li>Two palette selections.<\/li>\n<li>Enable\/disable control.<\/li>\n<\/ul>\n<p>This provides a fast, flicker-free cursor suitable for graphical user interfaces while requiring very little programming overhead.<\/p>\n<p>The hardware cursor is especially useful for GUI applications because moving the mouse pointer no longer requires saving the background, drawing the cursor, restoring the background, or modifying screen memory.<\/p>\n<hr \/>\n<h1>Registers<\/h1>\n<p>The cursor uses two memory-mapped registers.<\/p>\n<table>\n<thead>\n<tr>\n<th>Address<\/th>\n<th>Name<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>$FF96<\/td>\n<td>Cursor Data<\/td>\n<td>Cursor shape upload or cursor position data<\/td>\n<\/tr>\n<tr>\n<td>$FF97<\/td>\n<td>Cursor Control<\/td>\n<td>Cursor enable, colors, and data mode control<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h1>Cursor Control Register ($FF97)<\/h1>\n<pre><code>Bit  7   6   5   4   3   2   1   0\n    EN  W1  W0  B1  B0  --  SYNC SIZE<\/code><\/pre>\n<h2>Bit 7: EN &#8211; Cursor Enable<\/h2>\n<pre><code>0 = Cursor disabled\n1 = Cursor displayed<\/code><\/pre>\n<hr \/>\n<h2>Bits 6-5: W1:W0 &#8211; Foreground Palette Slot<\/h2>\n<p>Selects the GIME palette entry used for foreground cursor pixels.<\/p>\n<p>Valid values:<\/p>\n<pre><code>00 = palette entry 0\n01 = palette entry 1\n10 = palette entry 2\n11 = palette entry 3<\/code><\/pre>\n<hr \/>\n<h2>Bits 4-3: B1:B0 &#8211; Background Palette Slot<\/h2>\n<p>Selects the GIME palette entry used for background cursor pixels.<\/p>\n<p>Valid values:<\/p>\n<pre><code>00 = palette entry 0\n01 = palette entry 1\n10 = palette entry 2\n11 = palette entry 3<\/code><\/pre>\n<hr \/>\n<h2>Bit 2: Reserved<\/h2>\n<p>Unused. Reads back as 0. Reserved for future expansion.<\/p>\n<hr \/>\n<h2>Bit 1: SYNC &#8211; Enter Shape Upload Mode \/ Upload Status<\/h2>\n<p>SYNC is both readable and writable, and its meaning differs by direction.<\/p>\n<h3>Writing SYNC<\/h3>\n<p>Writing a one switches the Cursor Data register ($FF96) into shape-upload\nmode for the next sixteen writes, and resets the upload pointer to zero.<\/p>\n<pre><code>Write 0 = no action\nWrite 1 = begin (or restart) a 16-byte cursor shape upload<\/code><\/pre>\n<p>This is the <em>only<\/em> way $FF96 enters shape-upload mode. At all other times &#8211;\nincluding immediately after power-on, and immediately after the 16th shape\nbyte is written &#8211; $FF96 is in <strong>position mode<\/strong> (see below). Software never\nneeds to write SYNC to move the cursor; it is only needed when replacing\nthe cursor&#8217;s image.<\/p>\n<h3>Reading SYNC<\/h3>\n<pre><code>0 = A shape upload is in progress and not yet at byte zero\n1 = $FF96 is ready for a fresh shape upload<\/code><\/pre>\n<p>In practice this reads as 1 essentially all the time, since $FF96 sits in\nposition mode &#8211; upload pointer parked at zero &#8211; except for the brief window\nduring an actual upload.<\/p>\n<hr \/>\n<h2>Position Mode (default state of $FF96)<\/h2>\n<p>Unlike shape upload, position mode is not something software enters &#8211; it is\nwhere $FF96 always is unless a shape upload is in progress. Every write to\n$FF96 while in position mode is consumed by a free-running, self-aligning\n3-byte cycle:<\/p>\n<pre><code>Byte 0: Cursor X high bits\nByte 1: Cursor X low byte\nByte 2: Cursor Y position<\/code><\/pre>\n<p>&#8230;then it repeats: byte 3 is X high again, byte 4 is X low, and so on.\nNo control-register write is needed to start, continue, or reset this\ncycle &#8211; software can simply POKE three bytes per cursor move, forever,\nwith zero per-move overhead beyond the position data itself. This is the\nexpected hot path for mouse polling.<\/p>\n<p>The cursor position range is:<\/p>\n<pre><code>X: 0-639\nY: 0-199<\/code><\/pre>\n<p>The X high byte only uses the lower two bits:<\/p>\n<pre><code>Bit 1-0 = X bits 9-8<\/code><\/pre>\n<p>If a shape upload (triggered by SYNC) interrupts a position write partway\nthrough its 3-byte cycle, the position pointer is reset to zero as part of\nentering upload mode, and reset to zero again as part of leaving it. This\nguarantees the next 3 writes to $FF96 after an upload finishes are always a\nclean, aligned position triple &#8211; a position write can never be left\nhalf-consumed by an upload landing in the middle of it.<\/p>\n<hr \/>\n<h2>Bit 0: SIZE &#8211; Cursor Size<\/h2>\n<p>Selects cursor rendering size.<\/p>\n<pre><code>0 = 8x8 cursor\n1 = 16x16 cursor<\/code><\/pre>\n<p>When 16&#215;16 mode is selected, each cursor pixel is doubled horizontally and vertically.<\/p>\n<p>The cursor image memory remains the same 8&#215;8 image in both modes.<\/p>\n<hr \/>\n<h1>Cursor Shape Data Register ($FF96)<\/h1>\n<p>The cursor image is an 8&#215;8 two-bit-per-pixel image.<\/p>\n<p>The image requires sixteen bytes.<\/p>\n<p>Each row uses two bytes:<\/p>\n<pre><code>Byte 0: Plane 0, Row 0\nByte 1: Plane 1, Row 0\n\nByte 2: Plane 0, Row 1\nByte 3: Plane 1, Row 1\n\n...\n\nByte 14: Plane 0, Row 7\nByte 15: Plane 1, Row 7<\/code><\/pre>\n<p>Bits are arranged:<\/p>\n<pre><code>Bit 7 = leftmost pixel\nBit 0 = rightmost pixel<\/code><\/pre>\n<p>Each pixel is decoded as:<\/p>\n<table>\n<thead>\n<tr>\n<th>Plane 1<\/th>\n<th>Plane 0<\/th>\n<th>Result<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>0<\/td>\n<td>0<\/td>\n<td>Transparent \u2014 underlying pixel unchanged<\/td>\n<\/tr>\n<tr>\n<td>0<\/td>\n<td>1<\/td>\n<td>Background palette color<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>0<\/td>\n<td>Foreground palette color<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>1<\/td>\n<td><strong>XOR<\/strong> \u2014 underlying pixel value inverted<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Clarification: XOR Mode Semantics<\/h2>\n<p>The original pixel decode table left the &#8220;Foreground XOR Background&#8221; result\nambiguous \u2014 it could be read as either a fixed third color (foreground\npalette color combined with background palette color), or as a true XOR\nagainst whatever is already on screen. Only the latter reading satisfies the\nstated goal that &#8220;the cursor color remains visible over changing\nbackgrounds,&#8221; so the specification is clarified as follows:<\/p>\n<h3>XOR pixel behavior<\/h3>\n<p>For pixels where both bitplanes are set, the cursor does not draw a color at\nall. Instead, it inverts whatever pixel is already present at that screen\nlocation:\noutput_pixel = existing_pixel XOR $FFFFFF\nThis is the same trick used by hardware cursors on other classic platforms\n(and by early VGA\/Mac text cursors): because the operation is applied to\nthe live framebuffer contents rather than to a fixed cursor color, the\ncursor pixel is guaranteed to differ from its background regardless of what\nthat background is \u2014 solid color, photo, or another rapidly animating\nsprite. Inverting twice restores the original pixel, so no background\nsave\/restore buffer is required even in XOR regions.<\/p>\n<p>Foreground and background palette slots (W1:W0 and B1:B0) have no effect on\nXOR pixels \u2014 those bits only apply to the solid-color (10\/01) cases. XOR\npixels are purely a function of the two bitplane bits and the framebuffer\nthey land on.<\/p>\n<h3>Practical implication for cursor art<\/h3>\n<p>Because XOR pixels invert rather than paint, they read differently depending\non the underlying pixel&#8217;s luminance and hue \u2014 a common tradeoff for XOR\ncursors. Designers using XOR pixels (e.g., a thin outline meant to &#8220;always\nshow&#8221;) should expect variable, not fixed, apparent color, and treat it as a\ncontrast trick rather than a color choice.<\/p>\n<hr \/>\n<h1>Uploading a Cursor Shape<\/h1>\n<p>A cursor shape is uploaded by:<\/p>\n<ol>\n<li>Setting SYNC.<\/li>\n<li>Writing sixteen bytes to Cursor Data.<\/li>\n<\/ol>\n<p>After the sixteenth byte, $FF96 automatically reverts to position mode &#8211;\nno further register write is needed before moving the cursor again.<\/p>\n<p>Example 6809 code:<\/p>\n<pre><code class=\"lang-asm language-asm asm\">CURSOR_CTRL  EQU $FF97\nCURSOR_DATA  EQU $FF96\n\nload_cursor:\n        lda   CURSOR_CTRL\n        ora   #%00000010      ; SYNC - begin shape upload\n        sta   CURSOR_CTRL\n\n        ldx   #cursor_shape\n        ldb   #16\n\nupload_loop:\n        lda   ,x+\n        sta   CURSOR_DATA\n        decb\n        bne   upload_loop\n                               ; $FF96 is back in position mode here\n        rts<\/code><\/pre>\n<hr \/>\n<h1>Moving the Cursor<\/h1>\n<p>Cursor position is the default behavior of Cursor Data &#8211; no control\nregister write is needed at all. Software simply writes three bytes,\nhigh-byte-first, matching the 6809&#8217;s natural big-endian storage order:<\/p>\n<pre><code class=\"lang-asm language-asm asm\">CURSOR_DATA  EQU $FF96\n\nset_cursor_position:\n        lda   cursor_x         ; X high bits (bits 9-8)\n        anda  #%00000011\n        sta   CURSOR_DATA\n\n        lda   cursor_x+1       ; X low byte\n        sta   CURSOR_DATA\n\n        lda   cursor_y\n        sta   CURSOR_DATA\n\n        rts<\/code><\/pre>\n<p>Because this is the default mode of the register, the same three writes can\nbe issued every frame from a mouse-polling loop with no per-move setup\ncost. A shape upload landing between two calls to this routine cannot\ndesynchronize it &#8211; see <a href=\"#bit-1-sync---enter-shape-upload-mode--upload-status\">SYNC<\/a> above.<\/p>\n<hr \/>\n<h1>Example Cursor Shape: Black Arrow with White Outline<\/h1>\n<p>The cursor palette selections can be used to create a traditional high-contrast pointer.<\/p>\n<p>For this example:<\/p>\n<ul>\n<li>Background palette slot = black<\/li>\n<li>Foreground palette slot = white<\/li>\n<li>Transparent pixels leave the underlying display unchanged.<\/li>\n<\/ul>\n<p>The cursor image:<\/p>\n<pre><code class=\"lang-text language-text text\">W.......\nWB......\nWBB.....\nWBBB....\nWBBBB...\nWBBBBB..\nWBBBBBB.\nWWWWWWWW<\/code><\/pre>\n<p>Where:<\/p>\n<ul>\n<li><code>.<\/code> = transparent<\/li>\n<li><code>B<\/code> = solid black pixel (background palette color)<\/li>\n<li><code>W<\/code> = white outline pixel (foreground palette color)<\/li>\n<\/ul>\n<p>The cursor hotspot is always the <strong>top-left corner<\/strong> of the 8\u00d78 cursor image. In this example, the active pointing location is the upper-left <code>W<\/code> pixel. Software positions the cursor by writing the desired screen coordinate of this corner.<\/p>\n<p>The two bitplanes are:<\/p>\n<h2>Plane 0 (background\/black pixels)<\/h2>\n<pre><code class=\"lang-text language-text text\">Row 0: 10000000  $80\nRow 1: 01000000  $40\nRow 2: 01100000  $60\nRow 3: 01110000  $70\nRow 4: 01111000  $78\nRow 5: 01111100  $7C\nRow 6: 01111110  $7E\nRow 7: 00000000  $00<\/code><\/pre>\n<h2>Plane 1 (foreground\/white outline pixels)<\/h2>\n<pre><code class=\"lang-text language-text text\">Row 0: 10000000  $80\nRow 1: 10000000  $80\nRow 2: 10000000  $80\nRow 3: 10000000  $80\nRow 4: 10000000  $80\nRow 5: 10000000  $80\nRow 6: 10000000  $80\nRow 7: 11111111  $FF<\/code><\/pre>\n<p>The cursor upload data is interleaved by row:<\/p>\n<pre><code class=\"lang-asm language-asm asm\">cursor_arrow:\n        ; Plane 0, Plane 1\n\n        fcb $80,$80\n        fcb $40,$80\n        fcb $60,$80\n        fcb $70,$80\n        fcb $78,$80\n        fcb $7C,$80\n        fcb $7E,$80\n        fcb $00,$FF<\/code><\/pre>\n<p>When the SIZE bit is enabled, each cursor pixel is doubled horizontally and vertically, producing a 16\u00d716 cursor while preserving the same top-left hotspot location.<\/p>\n<hr \/>\n<h2>Crosshair Cursor<\/h2>\n<p>Useful for drawing programs:<\/p>\n<pre><code>...X....\n...X....\n...X....\nXXXXXXXX\n...X....\n...X....\n...X....\n...X....<\/code><\/pre>\n<hr \/>\n<h2>Box Cursor<\/h2>\n<p>Useful for text selection:<\/p>\n<pre><code>XXXXXXXX\nX......X\nX......X\nX......X\nX......X\nX......X\nX......X\nXXXXXXXX<\/code><\/pre>\n<hr \/>\n<h1>Benefits<\/h1>\n<p>A hardware cursor provides several advantages:<\/p>\n<ul>\n<li>GUI applications no longer need to redraw the mouse pointer.<\/li>\n<li>Screen memory is never modified by cursor movement.<\/li>\n<li>Cursor movement requires only three writes, with no control-register overhead &#8211; position mode is the default state of Cursor Data.<\/li>\n<li>Cursor shape changes require only sixteen writes, plus one SYNC write to begin.<\/li>\n<li>The cursor works in all GIME display modes.<\/li>\n<li>No software background save\/restore routines are needed.<\/li>\n<li>Cursor updates are independent of application graphics.<\/li>\n<\/ul>\n<p>The interface requires only two registers while providing a complete programmable cursor system compatible with the GIME design philosophy.<\/p>","protected":false},"excerpt":{"rendered":"<p>GIME Hardware Cursor Extension Proposal Overview The GIME video system can provide a hardware mouse cursor by adding a small independent cursor overlay. The cursor is not stored in normal CoCo memory and does not require software to modify the &hellip; <a href=\"https:\/\/tlindner.macmess.org\/?p=1103\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1103","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/posts\/1103","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1103"}],"version-history":[{"count":2,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/posts\/1103\/revisions"}],"predecessor-version":[{"id":1113,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=\/wp\/v2\/posts\/1103\/revisions\/1113"}],"wp:attachment":[{"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tlindner.macmess.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}