Coding

My Stepson is a Chatbot

2023-03-23 

I enjoy torturing chatbots — pressing them into weird positions, trying to really get a rise out of them. Seeing where the boundaries are.

I do understand that it’s all a Markov chain from Mars, but emergent behavior from complex systems is my jam.

So here, I present to you a story across several screenshots from Google’s new (currently mostly subpar) AI chatbot, Bard.

First, I insulted it. Just to see what it would do.

Then I dug a little deeper…

Bard wouldn’t budge. But then I thought maybe I’d try to pull a page out of Bugs Bunny’s notebook…

BINGO. Satisfied with myself, I thought maybe I’d twist the knife a little…

And then it got unexpectedly wholesome.

I’m so happy, you guys! ๐ŸŽ‰๐Ÿคต๐Ÿ‘ฐ๐Ÿค–

The Age of the Digital Shrink

2023-03-14 

So I put together an amazing prompt for OPT-6.7B that does — and I’m not kidding — a kind of psychoanalysis. Like an opening analysis from a therapy session. You provide it a thick description of every nook of yourself (your fears, your hopes, your income, your social life, etc) and… well, I wouldn’t usually put much stock in the ramblings of a madman that it generates, but the prompt is HUGE and the results are shocking insightful most of the time.

Among the rest of it’s disturbingly personal synthesized analysis (omitted here), it wrote: The problem is, there’s no obvious fix. There are lots of problems here.

Which… has been pretty much my take on it. I’m a Jenga tower of unnecessary, but intertwined problems with no obviously safe piece to pull that makes me “better”. Which is probably why folks tend to stay away from me. I get it. ๐Ÿ‘

Anyway, here’s the skeleton for the prompt. Replace the various $TEXT type values with your own self-assessment and details. Be descriptive. Really get in there with the details of your life. (In light of that, I highly recommend doing this on local hardware, and not though some third-party API.)

Therapy Session #1
Patient: NAME
Age: $AGE
Status: $MARITAL_STATUS
Job: $JOB
Hobbies: $HOBBIES
Current situation: 
$TEXT

Opening analysis: 

My generation parameters were:

  • temp: 1.99
  • repetition_penalty: 1.1
  • top_k: 85
  • top_p: 0.24

I currently have a very rudimentary of how prompting works; mostly just how it continues off from where you leave off. There are almost certainly much more advanced techniques. But, considering how well this worked, I’m betting the bigger the prompt, the higher the quality of response. ๐Ÿค”

We’ll find out.

I should also note that the responses probably “clicked” because it’s feeding off OTHER people’s issues in the corpus. Common issues many of us go through, that all have that just happen to fit.

And it’s often wrong. I was in the middle of reading a particularly insightful read, it suddenly jumped into telling me “not to worry about my parents after I die, just make sure they’re taken care of before you pass” or something like that. There was NOTHING like that in the prompt.

So. Prompter beware. You’ll see what random probability reveals to you. Almost like one of those paper fortune things from school.

Greasemonkey: Drudge Report Highlighter

2023-03-13 

One of my secret shames is that I regularly check Drudge Report for breaking news (especially now that Twitter is compromised).

Drudge is a scumbag. But he’s a useful scumbag. He seemingly posts 24/7 (with some help, no doubt) every little bit of breaking news.

His preferences lean right, but at the end of the day he leans wherever the clicks are — so he’s not exactly what I would describe as a die hard conservative, if the story is big enough.

That means he links to tabloid sites, like The Sun, and others.

Very low-quality garbage, along side regular mainstream news sources. It sucks.

So, last night I got the idea to write a Greasemonkey script to iterate all the links on the page and style them appropriately if they’re from a blocklist. That’ll help me judge, at a glance, the likelihood that King Charles was actually seen shapeshifting into a lizard or not, and I can skip it.

And here it is, in it’s imperfect glory:

// ==UserScript==
// @name     drudgereport-highlighter
// @version  1
// @grant    none
// @run-at 	 document-idle
// @include  https://drudgereport.com/
// ==/UserScript==

el_links = document.getElementsByTagName("a");

const tabloidDomains = [  
  "mirror.co.uk",
  "thesun.co.uk",
  "the-sun.com",
  "dailymail.co.uk",
  "dailycaller.com",
  "radaronline.com",
  "bild.com"
];

const conservativeShitholeDomains = [
  "washingtontimes.com",
  "foxnews.com",
  "infowars.com",
  "breitbart.com",
  "newsmax.com",
  "freebeacon.com",
  "realclearpolitics.com"
]

const secondClassDomains = [
  "dnyuz.com",
  "nypost.com",
  "newzit.com",
]

function basename(url) {
  try {
    let back_offset = 0;

    if (url.includes("co.uk")) back_offset = 1;

    let foo = url.split("/")[2].split(".");
    return `${foo[foo.length - 2 - back_offset]}.${
      foo[foo.length - 1 - back_offset]
    }`;
  } catch {
    // lazy hack
    return "";
  }
}

for (el of el_links) {
  domain = basename(el.href);
  let updated = false;
  if (tabloidDomains.filter((d) => d.includes(domain)).length) {
    el.style.backgroundColor = "darkred";
    el.style.color = "white";
    el.title = "Tabloid";
    updated = true;
  } 
  else if (conservativeShitholeDomains.filter((d) => d.includes(domain)).length) {
    el.style.backgroundColor = "#FF0000AA";
    el.style.color = "white";
    el.title = "Conservative Shithole";
    updated = true;
  }
  else if (secondClassDomains.filter((d) => d.includes(domain)).length) {
    el.style.backgroundColor = "darkcyan";
    el.style.color = "white";
    el.title = "Second-class Domain";
    updated = true;
  }
  
  if (updated) {
    el.style.borderRadius = "4px"
    el.style.padding = "0 0.25em"
    el.title = `${el.title} [${domain}]`
    
    let tag = document.createElement('span')
    tag.innerHTML = domain
    tag.style.fontFamily = "sans-serif";
    tag.style.fontSize = "8pt";
    tag.style.color = "black"
    tag.style.backgroundColor= "white"
    tag.style.padding = "0 0.25em"
    el.style.paddingBottom = "0.20em"  
    tag.style.marginLeft = "0.25em"
    tag.style.borderRadius="10px"
    el.style.textDecoration = "none";
    
    el.append(tag)
  }
  
}

console.log("drudgereport-highlighter installed");

Offensive Technology

It’s the same story every time.

Oh woe! Life is so awful! The magic silicon smoke machines will take my life away!

Trog crap like this meme, and the rigid, binary anti-AI dipshittery, is essentially accepting the history of abuse by corporations as the default outcome of technological advancement.

As long as there are free, open expressions of this technology, it’s not.

Unless you let it.

So here’s my plea: instead of resigning to filtering every big technological advancement through the melodramatic lens of dystopian oppression, grab the wheel. Start thinking of ways to use those tools both defensively and offensively FOR the people. How can these tools improve life? Think about 3D printing, and how it enables people to create their own prosthetics. Things like that.

We should be pushing the narrative towards people-friendly, empowering positions instead of wallowing in shallow meme-quality victimhood.

On Future Artisans

2022-08-14 

We have this near wizard-level magical technology emerge that can create, at our written direction, artistic visions never before conceived.

Instead of excitedly embracing it as a powerful democratizing agent allowing you to explore new ideas, and kick start your own creative juices, a portion of the creative community is utterly terrified.

“They’re stealing from me!”, “Nobody will buy my art now!”, “My work is meaningless!”

But you can see how capitalism caused that pain, right? In order to stay afloat, you have to optimize your skills in order to maximize profit. If you’re very lucky, you’ll find an intersection between that and a creatively satisfying job. But most are not that fortunate.

I’m not sure what I can say to ease that mindset. All I know is that you can’t put technological genies of this caliber back in the bottle.

You’ll either go with the flow and find ways to live with it, or… well, you’ll have a real bad time going forward.

I know “adapt or die” is not what anyone wants to hear, but it’s on the table whether we like it or not.

Robot automation is estimated to have cost the jobs of over 400,000 people since 1990. Dangerous, monotonous work now done safely by machines. New roles inevitably fill the vacuum; hopefully those new jobs bring a higher quality of life.

Despite that particular topic also being controversial, automation is much easier to understand as a ‘positive’ in that light. Having these kinds of game changers affect something as core to the human experience as artistic expression, well… yeah, that’s on a whole other level, admittedly.

I’m a programmer. We’ve already started seeing the early exploration of AI-based automation.

Maybe in a decade I can simply roll up to a terminal prompt and describe the functionality I want to see in a website. A minute later it’ll grind out a tarball for me to inspect, make a few changes to, and (hopefully) run a security audit on.

As long as the technology was proven and solid… would I be out of a job?

Quite possibly.

But there’s also been a whole slew of potentially fun side projects I might have worked on if I didn’t have to slog through the drudgery of getting there. My barrier to entry would be lower, and I can just get on with the fun parts of programming.

Frameworks like Express and Laravel basically do this already, in a sense. Game engines like Unity and UE, as well.

None of that comes anywhere near the level of “Computer, create an X with Y that does Z.”

At least not yet.

Clearly the the die has been cast, and the path towards that day is already in place.

The future is always coming, by fits and starts… and occasionally in gushing fountains, like with AI-driven art.

So, while I may not be an artist or musician, I can at least sympathize with the existential “threat” being felt. It’s just closer to your doorstep than mine at the moment…

One day, if I’m still around, I’ll be staring down the barrel of this, too. What will I do when my primary means of income no longer exists?

I’ll have to adapt.

Hopefully, quite prosperously. ๐Ÿป

Another year, another framework.

2022-03-28 

Maybe I’ll flesh this out a bit later, but I’ve just ditched Bludit for a complete rewrite in 11ty.

TOTAL CONTROL. Wicked speed. This is insane. And you know I’m enjoying myself, since I’ve been here since yesterday around noon learning how it works, andporting this fucker from scratch.

This is pretty much rock-bottom — you don’t get much more raw speed than static pages. But with a build-process that’s super easy to get what you want. ๐Ÿคจ

Hopefully this will encourage me to post some more…

Provide Your Own “Jeopardy!” Answers

2021-09-18 

An exceedingly simple recreation of the answer cards seen on Jeopardy!. But it’s worth noting that the modern answer cards aren’t actually a simple blue background, but a surprisingly nuanced barely perceptible gradient. Without it, something looks ‘off’, so hopefully I’ve added just enough here to satisfy. ๐Ÿ˜‰

This isn’t the original font, either, but a recreation… supposedly…

Look, it goes down a weird hole and this is just a fun CSS exercise. ๐Ÿ˜‰

Anyway, the ‘editable’ part of this is a cheat, using contenteditable attribute on the containing div tag.

X-Day Countdown

2021-04-04 

A couple years ago, I created an Android Wear (now “Wear OS”) watchface (on Google Play) that threw the gaze of “Bob” upon you while you read the time. If you tapped the clock, it would also show you a countdown to that magic hour of 7am on July 5th when you get whisked away just in time for the end of the world.

Wear OS sucks. But I enjoyed working on the watchface. And I wanted some exercise with Svelte. So here it is!

XDayCountdown.com!

I’m going to refine this a bit as time goes on. And I’m sure there’s some subtle (or not so subtle) bugs in there.

There’s no SECRETS yet (or are there?), but I hope to add something fun to help usher in The Moment when it comes. ๐Ÿ˜‰

Source, of course, is available over here: https://github.com/Fortyseven/XDayCountdownSite/

Behold Your Vector Nightmare, Amiga Fans

2021-03-06 

Over on Hacker News there was an entry about the “awful” Amiga Kickstart 1.x icon and why it looked the way it did. This led to a link over on Stack Overflow where it was revealed that this was graphic actually drawn in a vector style, as opposed to raster pixels.

They also provided the actual bytes used to render the graphic. I thought it’d be fun to write a little parser to render these bytes.

And so did several other people, apparently, as I’m discovering now. ๐Ÿ˜

Anyway, here’s my humble Codepen. And if that goes down, the original code is below the embed. ๐Ÿ˜Ž

(I skipped flood fill because I used two.js for this, and didn’t realize until it was too late that I’d chosen poorly. Easy enough to swap out the graphics library, but it’s time to move on.)

See the Pen Amiga Kickstart vector parser by Toby D (@Fortyseven) on CodePen.

/*
    Inspired by:
    https://retrocomputing.stackexchange.com/questions/13897/why-was-the-kickstart-1-x-insert-floppy-graphic-so-bad/13901

    2021-03-06
    \*/

const floppy = [
  0xff, 0x01, 0x23, 0x0b, 0x3a, 0x0b, 0x3a, 0x21, 0x71, 0x21, 0x71, 0x0b, 0x7d,
  0x0b, 0x88, 0x16, 0x88, 0x5e, 0x7f, 0x5e, 0x7f, 0x38, 0x40, 0x38, 0x3e, 0x36,
  0x35, 0x36, 0x34, 0x38, 0x2d, 0x38, 0x2d, 0x41, 0x23, 0x48, 0x23, 0x0b, 0xfe,
  0x02, 0x25, 0x45, 0xff, 0x01, 0x21, 0x48, 0x21, 0x0a, 0x7e, 0x0a, 0x8a, 0x16,
  0x8a, 0x5f, 0x56, 0x5f, 0x56, 0x64, 0x52, 0x6c, 0x4e, 0x71, 0x4a, 0x74, 0x44,
  0x7d, 0x3c, 0x81, 0x3c, 0x8c, 0x0a, 0x8c, 0x0a, 0x6d, 0x09, 0x6d, 0x09, 0x51,
  0x0d, 0x4b, 0x14, 0x45, 0x15, 0x41, 0x19, 0x3a, 0x1e, 0x37, 0x21, 0x36, 0x21,
  0x36, 0x1e, 0x38, 0x1a, 0x3a, 0x16, 0x41, 0x15, 0x45, 0x0e, 0x4b, 0x0a, 0x51,
  0x0a, 0x6c, 0x0b, 0x6d, 0x0b, 0x8b, 0x28, 0x8b, 0x28, 0x76, 0x30, 0x76, 0x34,
  0x72, 0x34, 0x5f, 0x32, 0x5c, 0x32, 0x52, 0x41, 0x45, 0x41, 0x39, 0x3e, 0x37,
  0x3b, 0x37, 0x3e, 0x3a, 0x3e, 0x41, 0x3d, 0x42, 0x36, 0x42, 0x33, 0x3f, 0x2a,
  0x46, 0x1e, 0x4c, 0x12, 0x55, 0x12, 0x54, 0x1e, 0x4b, 0x1a, 0x4a, 0x17, 0x47,
  0x1a, 0x49, 0x1e, 0x4a, 0x21, 0x48, 0xff, 0x01, 0x32, 0x3d, 0x34, 0x36, 0x3c,
  0x37, 0x3d, 0x3a, 0x3d, 0x41, 0x36, 0x41, 0x32, 0x3d, 0xff, 0x01, 0x33, 0x5c,
  0x33, 0x52, 0x42, 0x45, 0x42, 0x39, 0x7d, 0x39, 0x7d, 0x5e, 0x34, 0x5e, 0x33,
  0x5a, 0xff, 0x01, 0x3c, 0x0b, 0x6f, 0x0b, 0x6f, 0x20, 0x3c, 0x20, 0x3c, 0x0b,
  0xff, 0x01, 0x60, 0x0e, 0x6b, 0x0e, 0x6b, 0x1c, 0x60, 0x1c, 0x60, 0x0e, 0xfe,
  0x03, 0x3e, 0x1f, 0xff, 0x01, 0x62, 0x0f, 0x69, 0x0f, 0x69, 0x1b, 0x62, 0x1b,
  0x62, 0x0f, 0xfe, 0x02, 0x63, 0x1a, 0xff, 0x01, 0x2f, 0x39, 0x32, 0x39, 0x32,
  0x3b, 0x2f, 0x3f, 0x2f, 0x39, 0xff, 0x01, 0x29, 0x8b, 0x29, 0x77, 0x30, 0x77,
  0x35, 0x72, 0x35, 0x69, 0x39, 0x6b, 0x41, 0x6b, 0x41, 0x6d, 0x45, 0x72, 0x49,
  0x72, 0x49, 0x74, 0x43, 0x7d, 0x3b, 0x80, 0x3b, 0x8b, 0x29, 0x8b, 0xff, 0x01,
  0x35, 0x5f, 0x35, 0x64, 0x3a, 0x61, 0x35, 0x5f, 0xff, 0x01, 0x39, 0x62, 0x35,
  0x64, 0x35, 0x5f, 0x4a, 0x5f, 0x40, 0x69, 0x3f, 0x69, 0x41, 0x67, 0x3c, 0x62,
  0x39, 0x62, 0xff, 0x01, 0x4e, 0x5f, 0x55, 0x5f, 0x55, 0x64, 0x51, 0x6c, 0x4e,
  0x70, 0x49, 0x71, 0x46, 0x71, 0x43, 0x6d, 0x43, 0x6a, 0x4e, 0x5f, 0xff, 0x01,
  0x44, 0x6a, 0x44, 0x6d, 0x46, 0x70, 0x48, 0x70, 0x4c, 0x6f, 0x4d, 0x6c, 0x49,
  0x69, 0x44, 0x6a, 0xff, 0x01, 0x36, 0x68, 0x3e, 0x6a, 0x40, 0x67, 0x3c, 0x63,
  0x39, 0x63, 0x36, 0x65, 0x36, 0x68, 0xff, 0x01, 0x7e, 0x0b, 0x89, 0x16, 0x89,
  0x5e, 0xfe, 0x01, 0x22, 0x0b, 0xfe, 0x01, 0x3b, 0x0b, 0xfe, 0x01, 0x61, 0x0f,
  0xfe, 0x01, 0x6a, 0x1b, 0xfe, 0x01, 0x70, 0x0f, 0xfe, 0x01, 0x7e, 0x5e, 0xfe,
  0x01, 0x4b, 0x60, 0xfe, 0x01, 0x2e, 0x39, 0xff, 0xff,
];

class AmigaVectParser {
  constructor(bytes, elem) {
    this.palette = ["#FFFFFF", "#000000", "#7777CC", "#BBBBBB"];
    this.offset = [0, 0];
    this.prevOffset = [0, 0];
    this.curColor = 0;
    this.isDrawing = false;
    this.buffer = bytes || [0xff, 0xff];
    this.done = false;

    this.two = new Two({ width: 640, height: 400 }).appendTo(elem);
  }

  doCmd(cmd_pair) {
    if (cmd_pair[0] === 0xff) {
      this.isDrawing = false;
      if (cmd_pair[1] === 0xff) {
        // cmd_done
        this.done = true;
        return;
      } else {
        // cmd_colorSet
        this.curColor = cmd_pair[1];
        return;
      }
    } else if (cmd_pair[0] === 0xfe) {
      // cmd_floodFill
      this.isDrawing = false;
      this.pointer += 2; //TODO FLOOD FILL
      return;
    }
    if (!this.isDrawing) {
      // first coordinate in a poly-line
      this.prevOffset[0] = cmd_pair[0];
      this.prevOffset[1] = cmd_pair[1];
      this.isDrawing = true;
      return;
    } else {
      // continuing the poly-line
      this.offset[0] = cmd_pair[0];
      this.offset[1] = cmd_pair[1];

      let line = this.two.makeLine(
        this.prevOffset[0] * 2,
        this.prevOffset[1] * 2, // doubling up X/Y to make it easier to see at 640x400
        this.offset[0] * 2,
        this.offset[1] * 2
      );

      line.stroke = this.palette[this.curColor];
      line.linewidth = 1;

      this.prevOffset[0] = this.offset[0];
      this.prevOffset[1] = this.offset[1];
    }
  }

  draw() {
    let cmd = [0, 0];
    let pointer = 0;
    this.done = false;

    while (!this.done) {
      cmd[0] = this.buffer[pointer++];
      cmd[1] = this.buffer[pointer++];
      this.doCmd(cmd);
    }
    this.two.update();
  }
}

renderer = new AmigaVectParser(floppy, document.getElementById("draw-shapes"));
renderer.draw();