heckmeck!

Nerd content and
cringe since 1999

Alexander Grupe
Losso/ATW

The “hot spot” determines where your mouse cursor is pointing, i. e. the exact pixel that is considered the active pixel. This isn’t necessarily the top left edge:

For a mouse pointer of 16×16 pixels, like the classic Amigas had, you could encode the hot spot as two bytes in the range of 0 to 15, with (0,0) being the upper left corner. The actual implementation takes a different route:

  • The coordinates are negative – they specify how many pixels you have to shift the pointer up and to the left, relative to the active pixel, so that the hot spot overlaps exactly with the active pixel position.
  • A hot spot in the upper left corner isn’t encoded as (0,0) as you might assume.

Why is that? Apparently, this is related to a bug in the MoveSprite library call.

******* graphics.library/MoveSprite *********************************
*
*   SYNOPSIS
*       MoveSprite(vp, sprite, x, y)
*                  A0  A1      D0 D1
*   BUGS
*       Sprites really appear one pixel to the left of the position
*       you specify. This bug affects the apparent display position
*       of the sprite on the screen, but does not affect the numeric
*       position relative to the viewport or view. This behaviour
*       only applies to SimpleSprites, not to ExtSprites.

This can be corrected by adding one to the hot spot’s x value:

PointerCalculation
// hotspot_x = -7
// hotspot_y = -8
Hot spot coordinates as relative offsets: Take the active pixel position and add (-7,-8)
// active_x = 10
// active_y =  9
Example: Active pixel at (10,9)
// sprite_x = active_x + hotspot_x = 10 + (-7) = 3
// sprite_y = active_y + hotspot_y =  9 + (-8) = 1

MoveSprite(vp,pointer,3,1);
MoveSprite bug in effect: one pixel to the left, at (2,1)
// sprite_x = active_x + hotspot_x = 10 + (-6) = 4
// sprite_y = active_y + hotspot_y =  9 + (-8) = 1

MoveSprite(vp,pointer,4,1);
Increasing hotspot_x by one: sprite position correct

But… That’s not what’s happening in devs/system-configuration, the 232 byte preferences file that contains the colors and the mouse pointer image!

Take the classic default mouse pointer:

Preferences from Workbench 1.2

The selection pixel is at the second column in the second row – so (1,1) in “normal” coordinates, counting from the top left starting with zero. What would you expect the encoded hot spot coordinates to be?

  • (-1,-1) if you just negate x and y,
  • (0,-1) if you take the “1 pixel to the left” MoveSprite bug into account,
  • but certainly not (-2,-1)!

And yet, that’s what is stored on disk:

; devs/system-configuration

00: 0800 0005 0000 0000 0001 86a0 0000 0000
10: 000c 3500 0000 0001 0007 a120 0000 0000
20: 0000 fc00 7c00 fe00 7c00 8600 7800 8c00  bitmap
30: 7c00 8600 6e00 9300 0700 6980 0380 04c0
40: 01c0 0260 0080 0140 0000 0080 0000 0000
50: 0000 0000 0000 0000 0000 0000 0000 0000
60: 0000 0000 feff 0d22 0000 0fca 0002 005a   x=-2   y=-1  colors     
70: 0fff 0002 0f80 0000 0081 002c 0000 0000                colors    
80: 6765 6e65 7269 6300 0000 0000 0000 0000
90: 0000 0000 0000 0000 0000 0000 0000 0000
a0: 0000 0000 0005 004b 0000 0000 0001 0002
b0: 0020 0042 0000 0000 0000 0000 0000 0000
c0: 0000 0000 0000 0000 0000 0000 0000 0000
d0: 0000 0000 0000 0000 0000 0000 0000 0000
e0: 0000 0000 0000 0000                    

Instead of adding 1 to the x coordinate, 1 is substracted! What?!

There’s more: Prior to Workbench release 1.2, the preferences tool did store this hot spot as (-1,-1)!

Preferences from Workbench 1.1
; devs/system-configuration

...
50: 0000 0000 0000 0000 0000 0000 0000 0000
60: 0000 0000 ffff 0d22 0000 0fca 0001 005a   x=-1   y=-1 
70: 0fff 0002 0f80 0000 0081 002c 0000 0007
...

At least the graphics/sprite rendering in the ROM hasn’t changed. If you take this pointer in the old file format, i. e. with (-1,-1) as the hot spot, and move it into the upper left corner, only the black border on the top will disappear (correct), but the black border on the left will remain visible (incorrect).

Hotspot (-1,-1) across Kickstart versions

Still, it’s a mess! And the AmigaOS source code reflects it, too. There are constants like SPRITEERROR=-1 used in various places, and code comments like “oh, we need to compensate for that old off-by-one bug”.

No wonder things got confusing. Maybe there’s an overcompensation for the MoveSprite bug somewhere? Or a sign flip? That could explain why preferences adds -1 to x instead of +1. Regardless of the exact origin story of the hot spot origin –

Does it matter? Umm, yes. Totally! :) It has some funny implications:

  • Hot spot coordinates “in the wild” have
    • 16 different values for y (0 to -15) and
    • 17 different values for x (0 to -16).
    Out of 37,481 preferences files I’ve scanned, 639 had a hot spot x value of 0 – those were probably created with the old preferences tool. That’s about 1.7% of all pointers, but these are only the pointers where the hot spot was on the left-most column. For all the other pointers, you would have to look at them to determine if the hot spot is off by one or not…
  • There are mouse pointers with a hot spot outside of the actual mouse pointer! You can edit system-configuration in a hex editor and create a mouse pointer that cannot be moved near the upper left corner, no matter how hard you shove that mouse:
    Pointer with (0x7F,0x7F) as hot spot
  • When you open a (0,0) pointer in the new preferences tool, you can cause it to glitch out a bit when changing the hot spot.
  • Same when you open a (-16,-15) pointer in the old editor.

Well, I’m out of fun facts to squeeze out of this, and I haven’t even used the “What’s the point?” pun yet. :) Anyway, enjoy your bootable Amiga disk with an unusable mouse pointer that will drive you mad – or your loved ones:

PS: I stumbled upon this when I was writing a pointer editor of my own. It has a similar bug to the original preferences editors! Of course I’ll leave that in…

The other day I wrote about JavaScript and modern web things I enjoy. But I forgot to add some examples!

To balance out the “Frontend is the best OMG” vibes, here are some not so nice things in my current, yet-to-be-released project that I didn’t love:

  • When you process thousands of pixels, simple object lookups will add up and your rendering code will become very slow very quickly.

    // Slow
    for (all pixels) {
      if (x > canvas.width) doThis(); else doThat();
    }
    
    // Better
    let w = canvas.width;
    for (all pixels) {
      if (x > w) doThis(); else doThat();
    }
    

    Somehow obvious, but I was stunned how much of a performance impact this causes. “Why is my IDE frozen?”

  • To draw a clean rectangle with a 1 pixel stroke, you must use ctx.rect(x+0.5, y+0.5, w, h) to avoid smearing, because of course.

  • Chrome (and presumably every other browser, because it’s all* Chrome now) will sometimes fail to reload changed content from localhost, no matter how hard you’re ctrl-shift-force-reloading or power-doubleclicking. All the while it happily displays the correct, updated version when I view the source code! I have to go in and change some other bits here and there, and then Chrome might do me the honor of doing an actual reload.

  • I thought that substr and substring are more-or-less the same, but apparently the former is evil, irrelevant, and needs to be removed. Good luck with that!

  • Good old mouseover and mouseout gave me headaches because hovering over a child container within the element I want to track triggers an “out” event. That’s confusing, and we’re supposed to use mouseenter/exit instead. Hmm, okay. I am 100% sure I will have forgotten that by noon. Too bad! :)

Edit: One more thing which just cost me five minutes:

  • When you add content to a container like this:

    document.querySelector("#controls").innerHTML += "...";

    …that is very naughty (I assume). Also, and that’s what got me, any action listeners you had on elements within #controls will be gone! The whole inner HTML block gets replaced along with the listeners. D’oh!

*) Yeah, of course not all. Sorry, Firefox et al. Still upset about Opera’s discontinuation of their own engine… um, twelve years ago.

TikTok is horrible, and TikTok can be great. The same goes for the typography, mostly in the form of subtitles. Over a year ago I was wondering (German post) where all those “retro fonts” come from, as in: How do you even manage to render text without proper umlauts or accents in 2025?

The weird font engines that choke on characters that are not in plain ASCII and render them as alien moon glyphs are still around, but the trend in professional font usage is growing! At least in my feed there’s more and more instances with a proper capital sharp S being used for German:

I love this character in particular, so this makes me as happy as a video of a puppy befriending a horse, the sloppy vectorized HBO logo being expertly called out, or a cover version of “No surprises” performed by a professional harpist.

A piglet befriending a century-old turtle is fine, too. Errm, where was I? :)

PS: Aww shucks, I wanted to add a comic font specimen as well, but it turns out they’re using the wrong sharp S. :(

PPS: It seems the subtitle gods are listening. Would you look at that, a correct upper-case sharp S on TikTok using that same font:

Hmmm, this sounds weird as a blog post title. Let’s go for it! Of course I’m talking about the perspective of the backend programmer (you filthy pig), and my confession is this:

I like frontend stuff.

Well, not all of it, obviously (npm-dependencies *cough* left-pad *cough* ansi-styles), but the juicy low-level bits.

Edit: I’ve added some meat to this outrageous claim. :)

  • I get excited about native HTML tags: <dialog>, color pickers, calendars, <details>, <progress>
  • I get irritated (or excited?) when the default gadget colors change.
  • I pee my pants a little at the prospect of nested CSS rules without a preprocessor. Like… cas-ca-ding styles! In a sheet! Soon…
  • It is a rewarding exercise to figure out stuff like this, again and again:
    • What’s the CSS spell to prevent an element from outgrowing its parent?
    • How do I make an unordered list that can adapt its number of columns?
    • Quick, does the border width get added to the overall size or not?
    • What’s the format of data URLs, and how small can I crush this PNG?
    • Is the raw pixel format of image buffers ARGB or RGBA?
    • The holy grail: How do I center an element?
  • You can change the effing mouse cursor with a CSS declaration!
  • Oh yeah, JavaScript has /regex/ literals, keep forgetting that one.
  • It’s fast! You can emulate an Amiga in the browser! *

I guess I should point out: I’m not being sarcastic here. I know many backend developers seem to frown upon frontend stuff. That’s understable – work is work, and then there’s the whole npm/react/bloat hell. But pausing my backend work and turning to the browser is just… refreshing. Even better when I have a little side project where frontend and backend work hand in hand. Full control! Data! Pixels!

There’s enough horrible frontend stuff to be grumpy about, for sure. Horrible, ugly abominations of bloat and insanity. But coming from the age of Internet Explorer 4, table-based layouts, and the gentle beginnings of DHTML: You’re doing okay, frontend!

Oh, and enjoy the little mouse pointer some more, it gave me the impulse to write this post. :)

*) I know, I know, that’s not really new. Still awesome!

The other day, I noticed a tiny little Amiga gadget detail that had escaped me for 40 years. Today, for totally normal reasons, I was implementing a JavaScript routine that draws these very window gadgets. Turns out there’s another thing I haven’t spotted, ever:

The rectangles representing “this window” (white) and “the other window” (black) have different sizes! I guess that was deliberate, for reasons of optical harmony.

Yup, “fixing” that box to be the same size as the others looks weird, too:

But why wasn’t the horizontal retangle size increased as well? The white box with the background-colored outline looks so much narrower than its black counterpart! Anyway, Kickstart versions 2.0 and later sure did the right thing by abandoning the concept of two separate depth gadgets altogether.

previous next close