Chris McCormick - News https://mccormick.cx/news/ Chris McCormick - News en Copyright 2008- Chris McCormick 60 GMT chris@mccormick.cx mccormick.cx/news/ Jsfxr Pro Retro Sound Effects Generator entries/jsfxr-pro-retro-sound-effects-generator https://mccormick.cx/news/entries/jsfxr-pro-retro-sound-effects-generator It's been a while since I posted an update. So this is it! The tl;dr is I'm working on a sound effects generator micro-SaaS and I am nearly ready for launch.

05a0dbec2650463514391d169be4cd84.png

In February this year I was working on the iOS port of one of my music apps when I decided to pivot. Safari iOS bugs were taking a lot of time and really dragging me down. I decided to focus back on pure web based music applications. I love the web and I love making web audio apps.

I decided to convert one of my online web audio apps to a subscription micro-SaaS business. To do this I had to build a bunch of infrastructure. I was tinkering with server side ClojureScript and my new web framework, Sitefox (here is an interview I did about Sitefox on the ClojureStream podcast: https://player.fm/series/clojurestream-podcast-2504492/e75-sitefox-with-chris-mccormic).

I really wanted to build everything on top of this highly productive stack. So I made a plan:

  1. add authentication to Sitefox (ClojureScript backend web framework).

  2. make it easier to integrate Stripe (new library).

  3. use those two pieces to convert some existing apps into micro-SaaS apps.

  4. 🌱 open source as much as possible.

I've been carrying out this plan since February. I've added authentication to Sitefox and I've also made a Stripe integration library. Now I am in the process of doing #3 - converting an existing web app into a micro-SaaS app.

The app I have chosen to convert is a sound effects generator called Jsfxr. This is a wonderful little piece of software written by Eric Fredricksen. I started contributing to, and eventually maintaining this public domain codebase some years ago. At one point I put it up online under the domain sfxr.me. Some years later I checked the stats of the site and it was getting more than ten thousand hits per month. Wow!

Software developers generally hate marketing. We just love to build stuff. But it's difficult to get people to use the thing you have built, without resorting to all kinds of unpleasant shenanigans like telling people about what you built, and communicating with other human beings.

So discovering my existing site already had traffic was exciting. All I have to do is build something that the visitors might want. So that's what I've been doing - I've been working hard on a Pro version of Jsfxr. It's going super well. It has been really fun to work on this codebase using an all-ClojureScript stack, and I am just about ready for launch.

If you're interested you can read a log of the development process in this Twitter thread. I will post an update when it's ready for launch.

Thanks for tuning in!

]]>
/tags/all Wed, 02 Nov 2022 01:43 GMT
YouTube Cancelled My Nextcloud How-to Video entries/youtube-cancelled-my-nextcloud-how-to-video https://mccormick.cx/news/entries/youtube-cancelled-my-nextcloud-how-to-video Dear YouTube,

What the actual heck? Here I was, innocently demonstrating how to install Nextcloud on a Linux VPS server. I just want to help other people liberate their data. Nextcloud is super cool and powerful and I want to share the good news. My video got a bunch of views and people seemed to find it really useful. Hooray!

nextcloud-youtube-cancelled-3.png

Then suddenly, without warning, you can cancelled my completely innocuous video. I appealed, and you rejected my appeal with no explanation at all. You say my video contains "harmful and dangerous content". Really? What exactly is harmful and dangerous in this video? No reasonable person could find anything in this video that could be construed as "harmful".

nextcloud-youtube-cancelled-2.png

Is this really about protecting viewers? Or is it about protecting your parent company? In the opening frames of my video I spoke about replacing proprietary services like Dropbox, Google, and Apple. Was my video cancelled because it's dangerous to users, or because this idea is dangerous to your parent company?

The only remotely harmful and dangerous thing here is that people might switch away from centralized services like those provided by your parent company, to Free, Libre, and Open Source Software provided by awesome hackers working in the public good.

Of course, I have no recourse now. Your interface does not allow me any way to speak to a human being about this. I feel completely powerless to do anything about it.

I've read about this kind of thing happening to others, many times on Hacker News. I always thought it was just bad luck and that will never happen to me. I have also read this exact thing from people this happened to previously.


Here is the original video, hosted on Vimeo, just so everybody can see exactly how innocuous it is. It's literally a how-to video for installing Nextcloud on an Ubuntu VPS.

So if you're reading this and you host videos on YouTube, I implore you, please make sure you take backups. Make sure you have a second copy of your precious videos. Make sure you have a plan for hosting elsewhere in case you get cancelled too for no good reason.

If somebody from YouTube is willing to un-cancel my video and remove all strikes from my account, I will update this post.

Update: after engaging @TeamYouTube on Twitter the original video has been restored. I did not receive further correspondence from them. To migate against this happening again in future I will mirror my videos on multiple platforms.

]]>
/tags/all Sat, 02 Apr 2022 08:37 GMT
Come Warm Yourself By The Flaming Wreckage Of My Micro-SaaS entries/come-warm-yourself-by-the-flaming-wreckage-of-my-micro-saas https://mccormick.cx/news/entries/come-warm-yourself-by-the-flaming-wreckage-of-my-micro-saas 2b6ebc3b1260328e5baa16be6f8e3edc.png

Gather round friends for a true and epic tale of glorious Indie Hacking. How I built and launched a micro-SaaS product in 26 weeks. How I discovered the quiet joy of incremental progress. How I weathered a Hacker News ban and a barrage of sniping comments and landed the first paying customer, only to come undone at the last moment, defeat snatched from the jaws of victory. 26 weeks of work gurgling down the internet's drain hole. And finally dear reader, what I will salvage from the wreckage that may help us in future ventures.

That Wednesday morning I was up with the cock. 6am bright and early. The kids were still asleep and my wife too making this the perfect time to GTD. I creaked my way through the house into my office, opened up my XPS 13 and sat in the glow of Gvim, code sparkling from my fingertips.

This day I would be a veritable @jdnoc. I would put in those Deep Work hours, chipping away at the product, posting new articles for SEO, reading feedback, testing and triaging bugs. And so it went.

For 25 weeks I had done this. Half a year of one-day-per-week stone cold indie software development. Writing code, building in public on Twitter. I had sat down on days like this when I was pumped to work and code flowed freely, and also on days when I was grinding all day long sweating out every line from a creased forehead. I ignored my feelings, hot and cold, and incremented away on the feature set week by week. Today was no different.

By 2pm I looked at my GitHub project board. Tickets had whipped by like restaurant napkins in a New York harbour bluster. The "TODO MVP" column was empty. I had moved every ticket into "Done". A strange feeling came over me. This was it. This was launch day. An excitement grew in my belly as I began to plan the launch on Twitter, PH, IH, HN. This ship was set to sail.

The launch

I set the Product Hunt launch to start one hour later at 3pm my time. I posted a link on Hacker News with the title "Show HN: Download Twitter data without API keys". I drafted an Indie Hackers launch post with the links to give everything a boost. Then I shared the Hacker News link around with a few friends and on my Twitter account.

Then came the first sign. The first warning that fate was not to smile upon the little micro-SaaS that could.

screenshot.png

Hmm that's odd. It became apparent my Show HN post had been shadow-banned. I could see it when logged in but none of my friends were able to load the page. What was going on? I was overcome with confusion. For what possible reason would the link to my site be banned? I emailed the mods that evening without much hope of receiving a reply and went to sleep.

The next morning I woke to an email from a very nice Hacker News moderator. It was super polite and informative which I am grateful for. Here is a summary of what they told me. I'm sharing in case it is useful for your own HN posting:

  • The HN software thought I was violating the rule against using the site mainly for "self promotion".
  • The software starts filtering posts once the percentage of posts about your own stuff is too high.
  • It's best to have a track record of interesting links from other sources and occasionally post your own stuff - the software is adaptive so you can start doing that and you'll stop getting filtered.
  • The best posts for HN are ones that can't be predicted from an existing sequence of posts ("out-of-the-way topics that rarely get any attention").

The Hacker News moderator said my submission history did not look too bad and I should be able to get it back on track. They kindly offered to unblock my post and push it back onto the front page and finally gave the following tip about posting Show HN projects:

Add a comment to the thread giving the backstory of how you came to work on this, and explaining what's different about it. That tends to seed discussion in a good direction.

The bit about seeding discussion in a good direction turned out to be wrong, but I think this is on the whole good advice. A good rule of thumb is "be human" and "don't spam".

I had a busy day on Friday and in the evening I did as the moderator suggested and added a comment with the development story. I emailed the moderator back gratefully to let them know and went to sleep.

The first customer

The next morning I woke to an awesome surprise. The first paying customer! It's happening! They had paid $5 for the 24 hour access tier so they could download their Twitter favourites to a CSV file. They seemed to be having trouble downloading the data but I thought it must be some user interface issue and decided to check out how the Hacker News post had fared overnight before looking into it.

screenshot.png

A fantastic result. 1.5hrs on the front page and 25 comments! My server registered around 7,000 individual visitors to the site when normally there are 30 per day. I dived in to see what people were writing in the HN comments.

screenshot.png

screenshot.png

screenshot.png

screenshot.png

screenshot.png

screenshot.png

Uh oh.

The fall

Like a fool I ignored the noble sages of Hacker News. I was Prometheus to their Zeus. They watched in horror as I made off with the stolen fire of their secret Twitter API access codes, and brought it to all humanity in the form of an easy to use one-click interface. I had driven them mad with my barefaced democratic liberation of the Twitter API.

Alas they were right. I should have taken heed.

8409350e87736554966b25067bb81227.png

Very soon Hephaestus the god of Twitter API Authentication shackled me to the Mountain of Access Revocation. Just like that, the TweetFeast account was permanently suspended. All API requests broke instantly and my appeals to the Twitter API team went unheeded. Without Twitter API access there is no hope for my app and it was done for. Which brings us to today.

I refunded the one paying user. I updated the home page and closed the sign ups. My app now lies burning in the water, gurgling, bobbing gently downwards. A moment of silence, friends. Soon she will be subsumed by the waves of obscurity and the internet will know her no more. Alack, alas, so long little SaaS.

So ends my misadventure in Twitter API development and so ends this story.

Salvage

Well not quite. I must be able to salvage something from the ruins of this experience. What can we rescue and what can we learn?

1. Sharing the source code

First off, I am open-sourcing TweetFeast. If you are into LISP or Clojure or server-side ClojureScript you might find the codebase useful. Here are some highlights:

Hopefully there is stuff in there that is useful to people building full-stack apps in ClojureScript. Get the TweetFeast source code on GitHub.

2. General advice

If you're going to build something on a Big Corp API, be careful. Read the terms. Ask around and see if anybody else has done what you are trying to do. When you launch pay attention to the way you word the capabilities of your product.

I once worked on an app that went into the App Store with no problem despite violating a whole section in the terms of service. That was because some people inside Apple liked the app and gave it the green light. Rules at these companies are not laws. They are subject to individual whims and a lot depends on signaling.

I suspect a major reason TweetFeast was shut down is because of the way I worded the launch post. There are multiple products which do a very similar thing to TweetFeast (download CSVs of Twitter data) and have been running for years, and I suspect it is because they did not position themselves as an "API workaround".

The final take away is about posting on Hacker News. First of all, remember to be a good citizen and don't only post "Show HN" links. Share stuff that is interesting if you want to keep your self-promotion score healthy. Secondly, don't worry about the haters. Yes, the app got banned, but it also got a customer within half an hour of posting. Before that first customer a lot of people found the beta useful. Hacker News commenters often overlook the value that other people see (especially non-technical people). So don't take it to heart. They are wrong famously often.

3. What I got out of it

It might seem like this experience was an epic fail, but I actually got a tremendous amount of value from building and shipping TweetFeast.

  1. I got to road test my full-stack ClojureScript library Sitefox and it worked great.
  2. I got to hone my self-hosted deployment skills some more with Piku.
  3. I managed to overcome Shiny Object Syndrome, stay focused for 26 weeks, and ship goddamn it.
  4. I now know how much effort it takes to build a minimal micro-SaaS MVP (one month of full time dev days).
  5. I wrote a bunch of code I can re-use again (Stripe integration, Twitter auth).
  6. I learned a bunch of new stuff about UX, UI, design, and SaaS app architecture.
  7. I learned an important lesson about building on 3rd party APIs.

Finally, the best part. I get my day back. I get back one day per week to work on something else. That's very exciting!

What's next

I think I will re-use most of this code to try and build another micro-SaaS, but not on Twitter. A straight up web app most likely. My music apps are doing ok, and so I'll probably try to do something in this space. Whatever I do, I'll post about it here and you can also follow along on Twitter.

Thank you very much for reading, I appreciate it!

Onward. 👉

]]>
/tags/all Sat, 05 Feb 2022 07:07 GMT
A Space Ship For Lizards entries/a-space-ship-for-lizards https://mccormick.cx/news/entries/a-space-ship-for-lizards My son asked me to make a spaceship for his lizard toys. This is it.

ship.gif

screenshot.png

hoop-ship-render.png

Lizard persons not depicted.

]]>
/tags/all Mon, 10 Jan 2022 03:54 GMT
Doodle CSS HTML theme entries/doodle-css-html-theme https://mccormick.cx/news/entries/doodle-css-html-theme The other day I released Doodle CSS. It's a simple hand drawn HTML/CSS theme. You can use it make a web page look like a hand drawn mockup.

d77bd024c239dd5c922438b89a36f253.png

Since then it has garnered an astonishing ~500 GitHub stars, blowing past all of my other open source project repos. It made it to the front page on Hacker News and friends tell me it got featured in multiple tech newsletters.

It only took me a couple of days to put together Doodle CSS. The idea is one I've had for a long time though. The right combination of knowledge and motivation finally arrived last week and spurred me into action to implement it.

What a strange world it is building in public. Imagine spending your whole life as an open source developer with low-key popular niche repos, and then a literal scribble that took two days to make is what everyone latches on to.

It's useful feedback though, and that is the value in building in public. It seems like I've hit upon some deep seated doodle desire with this release. I feel like there is something bigger here I should develop. I will think about this some more.

For now I am happy with this as an open source project that other people are enjoying, and I'll admit the brief glow of popularity is very enjoyable.

]]>
/tags/all Sun, 19 Dec 2021 02:53 GMT
Doodle Rogue Tileset entries/doodle-rogue-tileset https://mccormick.cx/news/entries/doodle-rogue-tileset Hello reader! I'm excited to announce the release of the Doodle Rogue tileset. It's a free hand drawn tileset containing tiles and sprites that I used in my game Smallest Quest.

scene-goblin.gif

I thought it would be fun to set the graphics free and see what other people do with them. Let me know if you make something using Doodle Rogue!

]]>
/tags/all Fri, 26 Nov 2021 12:46 GMT
Build Full-stack ClojureScript Websites With Sitefox entries/build-full-stack-clojurescript-websites-with-sitefox https://mccormick.cx/news/entries/build-full-stack-clojurescript-websites-with-sitefox Sitefox is a back-end web framework for ClojureScript. I built it because I wanted to use ClojureScript to build websites and apps instead of Python and Django. It's inspired by frameworks like Django, Rails, and Flask.

Sitefox Logo

The easiest way to try it is to use one of the npm create scripts.

Use npm create sitefox-nbb for a simple website with no Java tooling dependencies.

Use npm create sitefox-shadow-fullstack for a full stack app using shadow-cljs.

Features

Here are the features it supports so far:

  • Routing
  • Database + Key-value store
  • Sessions
  • Templates (Reagent views)
  • Email
  • Forms
  • Logging
  • Live reloading

There are a ton of examples in the examples folder on GitHub.

If you build something cool with Sitefox let me know!

]]>
/tags/all Fri, 19 Nov 2021 01:12 GMT
Melody Generator iOS entries/melody-generator-ios https://mccormick.cx/news/entries/melody-generator-ios melody-generator-iOS-mockup.png

After many days of debugging (and filling out a bajillion forms) Melody Generator is finally available as an iOS app!

apple-app-store-button.svg

It's also available on the Google Play store.

You can still use the web app without installing anything at dopeloop.ai/melody-generator.

Enjoy!

]]>
/tags/all Mon, 15 Nov 2021 12:38 GMT
How I invalidated my best startup idea entries/how-i-invalidated-my-best-startup-idea https://mccormick.cx/news/entries/how-i-invalidated-my-best-startup-idea It's a simple fact that most startup ideas are not going to work out. Just like most tweets never get a retweet, most GitHub projects have no users, and most Hacker News posts never get an upvote. That's the simple truth.

If most ideas are no good the most efficient way to find good ideas is to discard the bad ones as quickly as possible. People talk about "validating" their business ideas by doing market testing, but "validating" has a bias towards a positive outcome. In reality the outcome is usually negative. So don't validate your startup ideas, invalidate them.

I've found it useful to take a scientific attitude. When you have a business hypothesis you need to run experiments to see if your hypothesis matches with reality. In the words of Richard Feynman, "if it disagrees with experiment, it's wrong." Just like Newton poking his eyeballs with knitting needles it helps to be a little detached. Not your retinas though. You'll need those.

237c179e06f8c49e31f5aa72eb7da2d1.png

This is the story of how I invalidated my greatest ever micro-SaaS idea.

A couple of weeks ago on my morning run, high as a kite on endorphins, I came up with a magnificent idea. I was going to change the world by helping noble open source developers get funded. I'd do this by helping people grow their GitHub sponsors with perks for their sponsors.

I spent the whole day doing some deeper research. There was so much going for this idea. Here is the list of "pros" I wrote down and breathlessly emailed to my entrepreneur friends:

  • viral loop (people link to the URL to use it)
  • proven concept (existing competitors)
  • makes money for people
  • creator economy trend
  • i personally know the customers

Foolproof. I started fantasizing about what I would say in my Indie Hackers Podcast interview when it hits $10k MRR. How one has to stay humble and wait for the big idea. How one has to work hard and stay focused. How one must listen to the customer you idiots, listen to the god damned customer!!!

The idea was perfect. Except for one thing. Nobody actually wants this software. I know this because I invalidated it.

I set a concrete goal. If I can't convince 100 people to sign up for the launch in 2 weeks then I will give up on the idea. John O'Nolan got 30,000 sign ups from one blog post about his idea before building anything. Surely if my idea is any good I can get 100 signups.

Invalidation #1 - friends

The first thing I did was tell some of my developer friends. Their feedback was interested but luke-warm and skeptical. None of them wanted this product. Nobody said "take my money!"

yeah I like your gh idea too but unsure if its good or bad

Ok, that's interesting. Maybe it's just my particular developer friends who don't want this? Friends try hard not to hurt or offend you so "your idea is ok" should really be downgraded to "your idea sucks".

I should also note that one of my friends is an open source developer actively taking donations. They should be the target demographic, but they were not very interested.

Invalidation #2 - search

It's always good to check search traffic. Is anybody out there looking for your solution already? You can check Google search volume, Reddit, and Twitter.

For Google search volume I use Ahref's free Keyword Generator tool and also the SurferSEO plugin.

Ahrefs said there are 80 searches for "github sponsors" per month. Not great. SurferSEO said 880 searches per month. That's better, but I trust Ahrefs more. The keyword is also very broad. Searches for more targeted keywords like "how to get github sponsors" were very low.

A Twitter search for "github sponsors" shows there is a lot of chatter. Out of all of the data I collected this Twitter chatter is probably the strongest pro-validation signal.

A Reddit search likewise shows a bit of traffic for people talking about "github sponsors" but the volume was lower than Twitter.

So I built a landing page

I created a simple signup page where people could get notified of the launch by signing in with GitHub. That should get high quality signups from real GitHub users. It could not be easier. All they have to do is click the "sign in with GitHub" button.

c42afca0681de1595fbe1b9f2404d73e.png

I wrote up the idea in the clearest way I could, explaining the benefits. This also allowed me to proof-of-concept the tech stack and GitHub API integration and make sure I could actually deliver the features. You can see the site at GHPerks.com.

Then I started the next round of invalidation testing.

Invalidation #3 - landing page

I posted about the site and the idea in a bunch of places.

  1. I wrote a tweet asking if I should pivot from my previous micro-SaaS idea to this.

screenshot.png

161 impressions. 4 people clicked through. No signups.

  1. I posted to my local Linux users group mailing list. It has thousands of open source people on it.

1 reply. No signups.

  1. I posted on Indie Hackers with the title "Looking for devs who want to grow their GitHub sponsors".

33 views. 3 upvotes. No signups.

  1. I posted a final tweet as I felt like the first one didn't really communicate it well.

screenshot.png

214 impressions. 12 people engaged. No signups.

In the end the MVP landing page had hundreds of open source developers visit and nobody signed up to hear about the launch.

This is a big warning sign that nobody wants this product.

Invalidation #4 - competitors

At the start when I first had the idea I did some competitor analysis. Did anybody else have this idea already and was it working for them? People often see competitors as a bad thing, but usually it just means there is a healthy market already where you can offer a differentiated product.

I re-discovered the story of Caleb Porzio who had grown his own GitHub sponsors. I found his MVP of a similar idea https://sponsorsyrup.com and remembered that I actually signed up for this thing.

It made me wonder why he hadn't posted any progress updates. Why had it not launched yet? I had never received any emails about it. I couldn't help thinking, is it because Caleb didn't get the interest he hoped for?

I also discovered https://onlysponsors.dev which is a similar idea. Eduardo has 84 sponsors on GitHub, but are they sponsoring him for this or for his work on Vue.js? From what I can tell there are not many people posting on the site.

This research helped me think about differentiators. What would I do differently? These projects both appear to be closed source but I would stay open source. I would also position it differently from Only Sponsors, and offer different features.

In the end this is an invalidation. Both of these people have huge followings on Twitter and GitHub and they have existing sponsorships. Only one of them has shipped and there doesn't seem to be much activity. With my small audience it would be a lot of work on the marketing and distribution side.

Invalidation #5 - target audience

I contacted some open source developers I know who have substantial sponsorship on GitHub. I even contacted devs who explicitly said they have this problem. There was some interest but not huge, and some strong warning signs:

Dev 1:

Currently I don't have any ideas for "sponsor" only content. I tried this for a while with my videos and secret links, but I gave up on that

When I tried something like this with the videos eventually I felt like I was spending too much time on pleasing sponsors with extra stuff rather than my OSS, so that would be one reason not to go there. When I asked my sponsors about this, most of them said: we are sponsoring for your projects, not for the perks, so you don't have to spend extra time on those perks.

As for special content: I did a few "hidden" videos but eventually I also just wanted to share that with everyone. I guess I'm just bad at keeping things a secret or away from people.

Dev 2:

I have done basically nothing to promote it. probably should be doing more on that front but its mostly organic

no rewards no. can't think of anything useful

Dev 3:

I’ve thought this through a few times and talked to a few other maintainers a while ago. My first impression was: The target group (open source devs) love to build things on their own and they don’t have money to spend. That’s both very tough to deal with. Also, I know some teams failed with similar products (probably for that reason).

This is some great feedback. These people are the exact open source maintainer target audience with existing GitHub sponsors. They are telling me no, we don't want this, and other people have failed at it already. Strong invalidation.

Conclusion: invalidated

The strongest message is the signups. Remember at the start I said I was aiming for 100 signups in two weeks? Only four developers signed up to hear about the launch. Three of them are friends and one is my brother.

My hypothesis was "this idea is so good I will get 100 signups in two weeks". Now I have strong empirical evidence to falsify it.

So this isn't the right app for me to build. It could still be a valuable idea for someone. The right person with a high level of passion and commitment might be able to make it work. Maybe Caleb or Eduardo will succeed with it (and I hope they do).

For me though it is nice to have given the idea a good chance and see it through to invalidation. Running this experiment was fun. Now that I have invalidated it I can skip the pain of building all the features only to discover nobody wants it. I feel very good about that!

]]>
/tags/all Thu, 21 Oct 2021 07:17 GMT
Bootstrapped Apps Update entries/bootstrapped-apps-update https://mccormick.cx/news/entries/bootstrapped-apps-update Bootstrapping a one-person software business is a long and winding road. I have the luxury of time and resources on this quest but I know others aren't so lucky. I'm interested in uncovering the secret sauce to launching businesses that work. I love open source and I want to open the sauce, pop!

I have three developments to report. Two experiments and one free tool you might find useful. Here goes.

Experiment #1

First off I want to tell you about an experiment which was a raging success! It was a raging success in the sense that I got an experimental result. The result was I was not able to exclude the null hypothesis. In other words it failed completely.

The Dopeloop Melody Generator online app is now receiving about 9k visitors per month. Most of them come from organic search for terms like "melody generator". My hypothesis was simple: some percentage of those 9k visitors will pay for a native version of the app.

It took me a while but I finally managed to get the paid version of the app in the Google Play store last week. I put a banner on the free online app linking to it and a sales page explaining the new features (mainly "download melodies as a wave file"). Then I went to sleep.

Here's what the banner looks like at the bottom of the app:

screenshot.png

The next morning I woke up to the first sale. Success! People were clicking through. If I even got one sale per day then this experiment would be worthwhile. It would prove to me that search-engine-to-product-purchase is a reliable channel.

Alas, there was a bug in this first version, and that sale was refunded soon afterwards. I fixed the bug and uploaded a new build.

One week later and there have been zero more sales. According to the stats very few visitors are clicking through from the online app to the paid app. Why? Are the features of the paid app not compelling enough? Are they on a different platform or OS? Are they just disinterested passers by? Now my task is to iterate on this channel and figure out the answers to some of these questions.

Experiment #2

Ok now for an actual success. Not a raging success but a more hopeful data point.

Last year I released a sci-fi roguelike game called Asterogue. The game took me 1.5 months of part time development to make. I marketed it by tweeting development updates, writing blog posts, and launching on a bunch of different sites and forums.

For my first real commercial game it was a moderate success. It had 38 sales on Android and 27 sales of the Windows version and made about $500 AUD. I was pretty happy with this outcome because I was fully expecting a total flop.

Games are generally bursty. All of the sales are made at the start and they drop off rapidly as the novelty factor wears off, and Asterogue was no different. One thing I kept noticing was the Android version was getting a trickle of ongoing users who were giving it five star reviews.

Super clean and streamlined Sci-fi roguelike without all the cryptic baggage. Definitely a hidden gem that I hope gets more exposure.

Super clean and streamlined Sci-fi roguelike without all the cryptic baggage. Definitely a hidden gem that I hope gets more exposure.

Despite some rough edges people seem to genuinely enjoy the game. This particular review got me thinking. How could I get more exposure for this game?

What I did was put the game on sale for $0 free free free for one week. I have read about other developers doing this and getting a burst of new users. My hypothesis is this: if I can get a burst of new users and exposure, the game will get a second wind of actual sales once the free period ends.

So far I have one day of data and the results have exceeded my expectations. More than 1000 new users on the first day of the sale. None of my games has ever had this many downloads before. Even ones that I gave away for free from the start.

I guess people really like free stuff. Even moreso, they like stuff that used to be paid and is now free. Zero-dollar sales are a great way to drive traffic to your project.

I don't know yet whether this will mean more people buying the game when the sale is over. Will soon find out.

Oh by the way you can still get the game for free if you want to try it:

A free tool for prioritizing

There are always too many things to do. There are more things on your TODO list than you can do in a lifetime. I used to get all tied up trying to figure out what to focus on. Paralyzed by indecision I'd end up not doing anything at all.

The RICE framework is a method of helping you prioritize. The formula is ( Reach x Impact x Confidence ) / Effort. I made a tool you can find at riceprioritization.com that uses this formula to help you rank your priorities.

You can use it on daily tasks, long term goals, or any other type of time/cost option you're considering. It's completely free and open source. Hope you find it useful, and any feedback is most welcome!

]]>
/tags/all Fri, 24 Sep 2021 03:18 GMT
What I'm working on right now entries/what-i-m-working-on-right-now https://mccormick.cx/news/entries/what-i-m-working-on-right-now We're just over half way through 2021 so I thought I'd post an update about the projects I'm working on right now.

tl;dr: music apps, a new game, and a micro-SaaS app.

e14731676178847bb6de32b9a97dcc92.png

A few months back I was feeling frustrated. I had a bunch of projects on the boil and I kept switching between them. I'd switch to whatever was most exciting at any particular moment. I felt like I wasn't making any progress. Context switching is the mind killer.

For a while I tried the 3-month-focus technique from this blog post. I couldn't sustain it. I'm too easily distracted by shiny new projects. I enjoy the thrill of chasing new ideas. Most of them don't pan out but some turn up genuinely interesting results, so I'd like to keep that optionality in my life.

I think I've finally found a balance. Shiny Object Syndrome versus getting things shipped. This strategy has been working well for me this year. What I've done is break down my projects into different areas of interest and then allocate one day to each area. I came up with these major areas of interest:

  • Music tech
  • Game development
  • Open source
  • Decentralization
  • Indie hacking (bootstrapping online businesses).

I assign one day each week where I only work on the one area at the exclusion of the others. I do the most impactful (or interesting) task in each area on the assigned day.

There is overlap between these fields. I am selling my music apps online at the moment so "Music Tech" is also part of "Indie hacking". One of my music apps is open source. Any work I do on decentralized technologies is part of "open source" too, etc.

Here was my side-project schedule for most of this year:

  • Monday = music tech coding.
  • Wednesday = indie hacking.
  • Thursday = open source & decentralization research.
  • Friday = game development.

Here is what my schedule looks like now:

  • Monday = music tech coding.
  • Wednesday = game development.
  • Thursday = indie hacking.
  • Friday = game development.

At the moment there are two days of game dev because I am crunching to finish Smallest Quest. I should be done in a month. Then I can give Friday back to open source software maintainance.

There's also client work of course. I work exclusively on client work on Tuesday. On the other days I do client work too, but I also give time to my side projects.

This schedule gives me enough variety to hold my interest and at the same time I feel like I am progressing each area. Here are the actual projects I am focusing on in each bucket right now.

Monday: PO LoopSync & Melody Generator

music-apps.png

I just shipped a big update to my pocket operator Android music making app. I've been working on this app for months. Last year I shipped an SLC and recently I've been pushing updates with new features.

Now that I have shipped that, I will switch to working on Melody Generator. People have been asking for features, and one specific feature I want myself is audio export. The biggest thing to do here is ship native apps. Melody Generator gets around 300 visitors per day. I am hopeful that some of those 300 visitors will convert to paying customers when I release native apps.

I also need to ship iOS ports of both of these apps. I'm working on getting the tech set up to do that.

8 bit music maker

screenshot.png

I've been tinkering this fun little music app called 8 bit music maker. It's a tiny tool for making 8 bit chiptune music. I'm not actually work on this on Mondays - I squeeze dev in between other projects. I've been having a lot of fun building this and it is just about at MVP. If you want to follow development of this app I am posting short updates on YouTube.

Wednesday & Friday: Smallest Quest

fightey.gif

Last year I released a sci-fi Roguelike called Asterogue. This year I'm building Smallest Quest. It's a new roguelike with the aim of being friendly enough for kids to play. My kids are pretty into turn based games which is what inspired me to make this. I came up with an asset pipeline that lets me hand-draw everything in the game and this has been fruitful and very also very satisfying work. It is nice to be able to just doodle out whatever asset I need in the game. I'm aiming to have this ready by the end of August.

If you want to follow progress on this project I post updates on a Twitter thread here. This is part of a new game development experiment I'm running with 3 friends. More on that later.

Thursday: TweetFeast

screenshot.png

My small success with selling apps and getting search engine channels working has convinced me to build another subscription software service. I've built two SaaS products already and both of them flopped. One was more successful than the other, gaining two customers, but neither took off in any meaningful way. There were a couple of things I didn't understand before:

  • Pick a real market with existing demand.
  • Figure out how to reach people in that market.

I think I have answers to those points with my new project. TweetFeast helps you download Twitter data without code or API keys which is a pain point for people who need to do analysis on Twitter data. There are products that do this already but not very well. Lots of people are searching for Twitter sentiment analysis solutions, so I am going to use that as a wedge into the market. TweetFeast provides sentiment analysis of tweets out of the box.

If you want to follow progress on this micro-SaaS project I have a Twitter thread where I post updates about it.

I'm using ClojureScript

Not all of my projects are built with ClojureScript but that's the direction I am moving in. Running ClojureScript "full stack" in the browser and on the backend in Node has been delightful, thanks to shadow-cljs. About half of my projects are now fully ClojureScript.

So that's everything I'm working on at the moment. I would love to hear from you if you have any comments or feedback.

Thanks for reading!

]]>
/tags/all Wed, 21 Jul 2021 10:31 GMT
How I beat Google at their own game entries/how-i-beat-google-at-their-own-game https://mccormick.cx/news/entries/how-i-beat-google-at-their-own-game Recently I hit a milestone. My procedural melody generator became the number one result for the Google search "melody generator". Even better, I beat out Google's own product "Chrome Music Lab".

f124fe00baa5c6e4310380e041ba439a.png

Ever since I set my sights upon the "melody generator" keywords I knew it would be an epic struggle. I knew that Google themselves held the top position. I knew that I would have to pull out all stops and hack at my best to attain the prize.

Well I did it. Google has 135,000 employees and I vanquished them all. I beat them at their own game. I climbed the greasy mountain of Google engineers and stood upon their faces with my heels in their eye sockets and my toes filling their nostrils and I thrust my flag aloft and howled victory into the wind. Search result perfection.

I know this won't last. Their internal systems will figure out why their product isn't number one and get it back up there. They'll stick a team on it with laser eyes peering machine-like from behind Google glassholes as they methodically dismantle my indie challenge.

They'll change the copy, or the product, or the algorithm. Like a scene from a Miyazaki movie I'll be swallowed up by the writhing mass of engineers and my software will be snuffed out like an unwanted kitten. This too shall pass.

I am at peace with this. All wins in life are but a momentary flash of brightness that quickly fades. Yes they will take away the ranking, but they can never take away the glorious memory. It's our story to tell to our grandchildren by the light of the scanlines. The noble tale of that one time a lone music tech hacker reached the top of Google and out-googled Google themselves. Perfection is possible my friends, it is simply transient.

PS To those friends of mine who work at Google, I hope it's clear this piece is tongue-in-cheek. Sorry about stepping on your face.

google-step.png

]]>
/tags/all Mon, 07 Jun 2021 12:14 GMT
One million people saw my dumbest tweet entries/one-million-people-saw-my-dumbest-tweet https://mccormick.cx/news/entries/one-million-people-saw-my-dumbest-tweet The word "meme" was coined by Richard Dawkins in his book The Selfish Gene. A meme is an idea that replicates itself from mind-to-mind, just as a gene replicates itself through organisms. The word "meme" is itself a meme, and it is spreading from my mind to your mind through the medium of this post.

We are in the midst of a pandemic caused by a real virus, and simultaneously, we are in the midst of a meme pandemic. Never before in history has it been possible for ideas to spread as quickly through populations as they do now. Just like DNA, some memes spread faster and wider than others. Some memes are fit.

Meme fitness is not the same thing as quality of thought. As I write this sentence it seems almost too obvious to write down. Stupid ideas can be just as fit as good ideas.

The central thesis of Twitter seems to be that good ideas bubble up. That fit memes are good ideas. Are they? Do good ideas bubble up, and are the ideas that bubble up any good?

Sometimes the stupidest ideas bubble up.

A few weeks ago I was the lucky host to two memes that infected my mind and reproduced to create a new meme. Naturally, like the good little meme vector I am, I tweeted the new meme. Here it is.

screenshot.png

Amazing right? This meme is somebody else's joke graph with a Mandalorian quote slapped on it. It took me 10 seconds to make. One million people saw this work of genius and ten thousand of them "liked" it.

This is absurd.

I've cut back my time on Twitter. Social media is the faustian bargain. It says, "play the game and earn attention. All you have to lose in return is control of your own mind".

Social media are the Trojans at the gates of your mind. Their wooden horse is magnificent. It glistens with all of the fittest memes. I let those Trojans into the fortress of my mind, and it was a mistake.

The way that Twitter controls your mind is with the numbers. Metrics are an addictive drug. Twitter is literally an incremental game. Click button. "Bring value". Number goes up.

I knew it was bad when I woke up at 3am thinking about what to Tweet. The only reason to be awake at 3am is if my kid needs a hug or my friend needs another drink. I knew it was bad when I found tweet-thoughts invading my mind on my morning run. My morning run is sacred. It's the only time I have experienced anything close to a stroke of genius. Instead of that I was twarting unstopple mental flatulence. Horrendous mind-farts. What a waste.

Twitter is useful. It is a tool, like a pencil, or an axe. It would be stupid to let a pencil control your mind, and it does not make sense to get angry at an axe. You should use your tools, and not be used by them.

Twitter does not use me any more. I use Twitter. I use it logged out. I use it intentionally, and on the schedule I set, and for the duration I choose. I do not accept the cookie from Twitter. I browse a smart person's timeline once a month as if it were a blog. I post occasional updates about what I am building. I use it to spread the word about the software I am writing and then I log out before it ensnares my mind. I've turned off all of the numbers with Calm Twitter by Yusuke Saitoh. I could not give a rat's ass how many likes I get. It's amazing.

Today on my morning run I saw the perfect arc of a leaf gliding down to rest on the pond. The surface rippled outwards. I smelled petrichor.

This is the way.

]]>
/tags/all Sun, 09 May 2021 01:21 GMT
What I learned bootstrapping side projects in 2020 entries/what-i-learned-bootstrapping-side-projects-in-2020 https://mccormick.cx/news/entries/what-i-learned-bootstrapping-side-projects-in-2020 In 2020 I made $693 USD from side projects. I contributed to several open source projects and made about a thousand commits on GitHub. I shipped a commercial game, a SaaS product, an IDE, two music apps, a handful of open source utilities, and a couple of websites. I love building stuff!

What I learned:

  • Find demand before building something.
  • Single-pay products are easier than SaaS.
  • Build in public to boost the launch.
  • Ongoing updates help people find the thing.
  • Build websites to help people to find the thing.
  • Being open source does not hurt sales.

The side project income mostly came from:

The SaaS products I worked on failed to generate more than a few cheeseburgers. 🍔 Good thing I love cheeseburgers. If you're doing a first time project and you want some quick wins then probably don't start with SaaS.

Most games follow a typical pattern. They earn all the money in a big burst at the start and then the income falls off to zero. I've heard this from talks by other indie game developers as well. There is a novelty factor in games. This was true for my game too.

I mainly got the word out on Asterogue by building the game in public. What that means is I posted in multiple game dev forums about progress as I was making the game. It was a big effort and the launch went alright as a result (as opposed to most projects which launch to crickets). My fastest sales were from Asterogue but they've fallen to zero now.

The two music apps have done better. I've had 3 months of steady sales from them. They're making a combined revenue of about $80 USD per month.

Beat Maker 3-month revenue

Beat Maker 3 month revenue AUD

PO LoopSync 3-month revenue

PO LoopSync 3 month revenue AUD

I tried something new with PO LoopSync. I found demand before building. I studied a forum where enthusiasts of pocket operator devices hang out and I took notes. The notes revealed patterns in what they find important. Once I figured out what they want I made some mockups and asked them if it was what they wanted. When they said yes I built it.

This worked well and sales were good right from the start. Building stuff that people already want is more fun for everyone.

I've been building in public on different projects for a while now. It basically means I tweet about what I am making. Also, this very blog! When I launch something, people such as yourself can find out about it easily and tell others. That helps a lot to get the word out. Thank you for that!

I actually don't like building in public very much. It's uncomfortable. I especially don't like social media. So I've set up a system that allows me to interact with social media in an efficient and minimal way, but still be present. Maybe I'll post about that some time.

My problem up until now has been the bang-then-crash of launches. Initial interest and sales that then trail off. I've found two good ways to fix that this year. I guess you'd call these activities "marketing".

The first one is to post maintainance updates. Every time I update an existing project I try to post something about it. This probably seems obvious to a lot of people but it wasn't obvious to me. Each time I post an update I notice a small spike in interest and/or sales.

For example there was a gamejam for roguelike games this month. Leading up to the jam I made weekly updates to Roguelike Browser Boilerplate. I posted about them on the roguelike sub-reddit forum in their "saturday sharing" section. A bunch of new sales came in! Posting updates works.

The second thing I have tried is building useful websites that link to my apps. Marketing people call this SEO. I call it "building a useful website that links to my app". Not as catchy I guess. My friend Tobias showed me how effective SEO can be. There is also a good article about SEO by jdnoc that I learned from. Some people use SEO in a deceptive way but for me it's about giving my work the best chance to be found by people who are looking for it.

The first site I built is all about pocket operators. I used the research I did earlier to make a page about the most useful stuff. This got me on the front page of search for the phrase "pocket operator apps". Lo and behold one of my apps is a pocket operator app! So people find the page and then they can find my apps too. Here's a graph of the search volume on that site:

screenshot.png

Another site I built is a free web app for generating random melodies. It's a procedural melody maker that generates midi melodies you can download. There's a link from the free web app to my other apps.

The name for this is apparently "side project marketing". So my side projects are being marketed by my side-side projects. The free web app shows up on the front page of search for a group of terms around "ai midi melody generator". One of the apps it links to is a random beat generator so the audience is very similar. Here's the graph of search volume for the melody generator:

screenshot.png

So I think these two sites are a fairly steady channel where people find my apps. They're searching for "pocket operator apps" and "melody generator" and they find those sites and then they also sometimes click through and buy my apps.

There is one other way people are finding the apps. I have a free app on the Play store. It's an app for building music apps. So people who are already interested in music apps and development can find that, and then some of them also find my other apps.

The final thing I will note is that being open source has not hurt sales. Beat Maker is open source and I haven't open sourced PO LoopSync yet. So it's an imperfect A/B test but it's still useful. There isn't really much difference in the sales. There is basically only upside to being open source for somebody at my scale. People like it, and it builds trust, and it fits my ethics.

Anyway, I hope this is useful to somebody. I'll continue to post updates about my dev adventures and things I have discovered.

]]>
/tags/all Wed, 24 Mar 2021 23:29 GMT
A Hand-doodled Roguelike Tileset entries/a-hand-doodled-roguelike-tileset https://mccormick.cx/news/entries/a-hand-doodled-roguelike-tileset I've been working on a free hand-doodled roguelike graphics tileset called Doodle Rogue. It's an alternative to the standard console and pixel roguelike graphics tilesets out there.

I'm relatively new to drawing. It's taken a couple of years of drawing badly to get to the point where I am comfortable enough for a tileset. This post is to show you some betas of the tileset I'm working on, and also the art that has inspired this work.

Doodle Rogue work-in-progress sketches

Here are the tileset beta sketches.

Doodle Rogue scenery sketches

Doodle Rogue scenery sketches

Doodle Rogue item sketches

Doodle Rogue item sketches

Doodle Rogue character sketches

Doodle Rogue Characters sketches

Doodle Rogue mockup sketch

doodlerogue-mockup.png

There is a ton of work still to do on these. I need to redraw them as vectors and clean them up, and finsh drawing the remainder of the tiles.

Research & references

I used Pinterest to explore different reference styles for the tileset. Here are some of the artists I am using for inspiration and referencing. There are actually a lot more references than this but these are some anchor points that have stood out.

You can find my game-draw reference pinterest feed here if you care to follow along.

Dom 2d

bcf576cf19a7b5075d8cbfb70be1b06e.png

2277f9dc389942e29ce37969e583637a.png

3068953c09c073fefadd2203884b1bec.png

Dom2d has been a big inspiration for my drawing in general. I love the balance he strikes between quick-draw simplicity whilst still looking good.

Slowquest (Bodie H)

slowquest.png

I love the gritty detail in Bodie's drawings of items and dungeons. Studying his work has taught me a lot about texturing with lines.

Timecowboy

fb5d302f9953831353a3ac0bf976cc49.png

ea26c3c5a6c45d8e8747ac0fb34fb9b9.png

Timecowboy is a web comic artist.

Varguy

3b438d2dfcadecbb6c656fd7f8424287.png

Varguy has the rare skill of the ligne claire artists, combining seemingly simple shapes and lines into images that really come to life.

Mike Yamada

065373d751bf6c2b5606fa5ad2b4627d.png

21e97cdfde20f4ab5266003599439ad6.png

a0248a2048cf0640a8bceb0fd3d4fb8f.png

I found Mike Yamada's work when reading The Noisy Garage to my kids. I love his animal characters.

Moebius

3c56504ac0aec329ffcdb1e22e7b1c6c.png

790be18ced20bf0a9713ecc2d7441965.png

That's everything for today. I hope you've enjoyed these images.

]]>
/tags/all Sat, 30 Jan 2021 12:29 GMT
A procedural MIDI melody generator entries/a-procedural-midi-melody-generator https://mccormick.cx/news/entries/a-procedural-midi-melody-generator I've just released a melody generator that I've been working on for a while. It's a small web app that you can use to procedurally generate looping MIDI melodies and then use them in your own music.

The fractalesque algorithm it uses to generate melodies is one I came up with when I was writing a lot of algorave music a decade ago. The MIDI melodies are rendered to sound using the wasm port of Timidity by Feross.

Enjoy!

]]>
/tags/all Wed, 06 Jan 2021 07:26 GMT
Random Hip Hop Beat Generator App Re-launch entries/random-hip-hop-beat-generator-app-re-launch https://mccormick.cx/news/entries/random-hip-hop-beat-generator-app-re-launch In October 2010 when the Android store was just a spring chicken, I built a minimal open source app that generates random hip hop beats called "Can of Beats". I had been getting into procedurally generated music and so I was able to build the app in a matter of a couple of weeks, and then I uploaded it onto the Android store just to see what would happen. It sold ok, paying our internet bill for a while, but as I had no idea what I was doing, I soon got bored of the low sales numbers and basically ignored it for 10 years.

Over the lifetime of the app it generated a couple of thousand dollars worth of sales. Knowing what I now know about the compounding effects of building things in public, I should have spent the 10 intervening years doubling down on that first trickle of installs, and shipped a ton of minimal audio apps, one after the other. Ah well, hindsight is 20/20!

They say the best time to start compounding any investment is yesterday. So in the spirit of that I've been working through November on some new minimal audio apps, and I'm also re-launching the original beat generator app with some new features!

Random hip hop Beat Generator Android 
app

Get it on Google Play

One feature I'm particularly excited by is the Pocket Operator sync. If you don't know what this means, don't worry, it's for a tiny niche of people who are super into it. If you are curious about what a Pocket Operator is, look it up, they're awesome!

]]>
/tags/all Thu, 26 Nov 2020 09:30 GMT
Bootstrapping and Convexity entries/bootstrapping-and-convexity https://mccormick.cx/news/entries/bootstrapping-and-convexity Nassim Taleb has this idea of "convexity". There's a bunch of complicated maths but it's actually quite a simple concept once you get it. This concept is useful for indie hackers and people bootstrapping businesses, because it gives you a general strategy that works in the presence of high levels of uncertainty (i.e. real life).

Is your business idea any good? Is there a market? Is the timing right? None of these questions matter so much if you choose convex processes.

So what is convexity? If a process is convex it means it has a big potential up-side and a limited potential down-side. Take a look at this graph and imagine your project is a marble that is pushed randomly to the left or right.

Taleb convexity 
graph

If it is given a push to the left then it will roll down the slope. If it is given a push to the right it will go up the slope. You can clearly see that there is a maximum depth the marble can go if it goes left, but it can go much higher if it goes to the right. Given enough force to the right it's going to fly off and your project will make a million dollars! So the down-side is limited, but the up-side is unlimited.

If some activity maps to this graph then you can say it's convex and those are the types of things you want to do. What you want to avoid is activities or processes where the opposite is true: all pain and no gain.

One of Taleb's insights is that we should not worry about the direction of the push on the marble because we simply can't know it. It's an uncertain world and the force on the marble is effectively random. We only fool ourselves if we pretend to be able to predict it. How many times have you launched something or tweeted or posted and not had the result you expected? You're going to be wrong a lot. Convexity embraces being wrong a lot.

So how does this apply to bootstrapping a business? All you gotta do is make sure you are picking convex activities at each step of your project. Decide if a given process or activity or action has a limited down-side and an unlimited up-side, and do it if so. Do lots of these convex activities to uncover reliable up-side and run-away successes.

Non-convex indie hacker processes (bad)

Here are some examples of non-convex indie hacker processes that you should avoid:

Quitting your job to go all-in on your unprofitable side project. This is non-convex because the down-side is ruin. If things go wrong you end up on the street with no job and a failed startup.

Spending a long time building without shipping. This is non-convex because the down-side is a loss of an unbounded amount of your most precious resource: your time on this earth.

Taking VC funding. Contraversial I know, but I beleive this is non-convex because you are locked into one business, beholden to the whims of investors, and you have given up all optionality (another Taleb idea I'll discuss below). Basically there is unlimited down-side in being beholden to somebody else's goals and not being able to do anything about it because they hold the purse strings.

Taking out a bank loan before you have paying customers. Taking on debt seems to be non-convex generally because of the unbounded way debts can grow.

Convex indie hacker processes (good)

Retaining some freelance hours while you work on side projects. This is convex because the down-side is limited (you still get to eat if you fail) and the up-side is unbounded if one of your side projects takes off.

Time-boxing your development and shipping frequently. This is convex because the time you lose if you build something that nobody wants is capped, but something you ship might actually catch on.

Posting on Twitter, Hacker News, and marketing in general. Marketing is convex because there is only a small amount of reputational risk and you have to be reeeally annoying to reach that level. The unbounded upside is having your product discovered by a market that really wants it.

Building in public. This is convex for the same reason as marketing. If you fail, or look like a fool, it's just not that bad. Upside is exposure, reputation, and viral adoption of what you're doing.

12 startups in 12 months. This strategy looks insane but if you look at how it works out for somebody like Pieter Levels it is convex. It's common for people to not finish their 12 startups and often the reason is because they also have the option to exit early on success (see below). It's a safe way to practice Taleb's "systematic convex tinkering".

Optionality

Another property you want is optionality. Let's take the Pieter Levels 12-startups-in-12-months example. Here you not only have convexity but you also have the option to take one of the 12 startups and run with it. Nobody is going to care if you stop at startup number 7 because it became a raging success that demands your attention. This is in fact exactly what Levels did. He launched Nomad List and Remote Ok, and then when it was clear they were popular, he took the option and went back and pumped them up.

So you also want to do things where you have the option to retain any up-side that is achieved.

Marketing is another good example. Lets say you post on Hacker News ten times and nine of those times you hear crickets, but one of the times your post blows up. Optionality means capturing that attention somehow so you can use it again later. So for example you might have an email list that people can opt in to. When a post does well you capture the up-side by letting interested people subscribe to your list.

Side note: are successful founders just lucky?

Elsewhere Taleb and Ole Peters and others talk about the concept of ergodicity, and two different types of probability: time vs. ensemble.

Ensemble probability is when you look at all indie hackers, and then the sub-set of "successful" indie hackers, and then figure out the probability of indie hacker success based on those two numbers. When I wrote that post which blew up it was about the ensemble probability of indie hackers, so it is actually a bit misleading because ensemble probability is the wrong way to look at it.

Time based probability is when you take a single indie hacker running many experiments, and look at their journey over time. It's the probability that one of their business experiments goes big.

The safest place to be is in the second category, running many convex experiments over time with no chance of ruin on any given experiment. You want to take the time based view because that is the one you actually have in real life. You are one single person, not many people in parallel.

Some successful founder stories are actually only visible in the ensemble category. They made one thing, got the golden ticket, and made a million dollars.

When taking the advice of successful founders you would do well to look at whether they are ensemble or time based. Did they try a ton of different ideas before succeeding? Good. Have they had more than one success or just one? Time based successes are the ones you want to learn from because they are repeatable. Individual successes from the ensemble are less useful as they may be due to good luck rather than good processes.

Conclusion

Run many business experiments sequentially, cap the down-side (time/cost/ruin), retain the option to capture any up-side.

Good luck! You won't need it.

PS You have the option of following me on twitter to find out about stuff I'm making. ;)

McCormick convexity 
graph

Footnote: I hope I have understood Taleb's ideas correctly, but if I have made any mistakes they are 100% my own.

]]>
/tags/all Thu, 05 Nov 2020 03:32 GMT
It's Asterogue Launch Day entries/it-s-asterogue-launch-day https://mccormick.cx/news/entries/it-s-asterogue-launch-day Since I was a kid I've always loved the roguelike genre. These procedurally generated mostly-text-based games have a wonderful depth and I've long been fascinated by the leverage and replayability you get from the procedural aspect.

Asterogue gameplay 
gif

For the past 10 weeks I've been working on my own roguelike. It's a sci fi game with tile graphics set in the interior caverns of an asteroid. I've had heaps of fun building this and I hope you'll have as much fun playing it.

Here's the announcement on Twitter if you care to help me out with a retweet. Thanks so much!

Enjoy!

]]>
/tags/all Fri, 30 Oct 2020 08:56 GMT
Roguelike Browser Boilerplate entries/roguelike-browser-boilerplate https://mccormick.cx/news/entries/roguelike-browser-boilerplate Recently I launched this web based template project that you can use to make your own roguelike game. If you've ever wanted to make your own roguelike game and you know a bit of web development then this is for you.

I'm also recording a series to screencasts which show you how to customise the template to make your own game. It's a walk-through of the whole process, basically a graphical javascript roguelike tutorial which you can follow along with.

Enjoy!

]]>
/tags/all Mon, 31 Aug 2020 07:27 GMT
Space Elk's Lounge Room entries/space-elk-s-lounge-room https://mccormick.cx/news/entries/space-elk-s-lounge-room DSC_0002.JPG

]]>
/tags/all Fri, 14 Aug 2020 01:50 GMT
In The Wilds entries/in-the-wilds https://mccormick.cx/news/entries/in-the-wilds DSC_0037.JPG DSC_2425.JPG DSC_0052.JPG DSC_0046.JPG DSC_2454.JPG DSC_0053.JPG

]]>
/tags/all Wed, 22 Jul 2020 04:32 GMT
Slingcode Is Out entries/slingcode-is-out https://mccormick.cx/news/entries/slingcode-is-out At the end of June I finally shipped Slingcode, the browser based code editor I've been working on for several months.

The response was overwhelming:

  • ~22,000 visitors to the site.
  • ~7,000 uses of the Slincode app.
  • ~2,400 YouTube views of the intro.
  • Made the front page of Hacker News.
  • 100+ reactions on dev.to.
  • 57 retweets of the launch.

This has been a humbling experience and I'm grateful to every person who checked it out and those who gave me feedback. Thank you!

Since then I've been recording screencasts for getting started with technologies like React, Vue.js, and SVG live-coding in Slingcode. You can find the screencasts at slingcode.net/screencasts.html or on the YouTube playlist embedded here:

I hope this tool and the screencasts are useful to you.

Enjoy!

]]>
/tags/all Fri, 17 Jul 2020 01:51 GMT
Un-template Python HTML Library entries/un-template-python-html-library https://mccormick.cx/news/entries/un-template-python-html-library Un-template is a minimal Python library to modify and render HTML. The idea is to start with a pure HTML document, choose the bits you want to modify using CSS style selectors, and then change them using declarative Python datastructures. This means you can use pure Python to replace sections of HTML and do all of the "template logic", similar to how declarative front-end frameworks work.

Install it with pip install ntpl.

Here's an example. It's a Django view function that returns a web page. A file called index.html is loaded, two HTML tags with IDs content-title and content are replaced with new content. A link tag with ID home is replaced completely with a new link tag.

from ntpl import slurp, replace, replaceWith, render

template = slurp("index.html")
faqs = markdown(slurp("FAQ.md"))

def faq(request):
    html = template
    html = replace(html, "#content-title", "FAQ")
    html = replace(html, "#content", faqs)
    html = replaceWith(html, "a#home", render(["a", {"href": "/"}, "home"]))
    return HttpResponse(html)

That's basically all there is to it. You can use it like this instead of Django's native internal templating language.

You can get the source code on GitHub or install it with pip.

It is inspired by Clojure's Enlive and Hiccup, this library uses pyhiccup and Beautiful Soup to do its work.

]]>
/tags/all Tue, 02 Jun 2020 11:36 GMT
The Zero Customer Heuristic entries/the-zero-customer-heuristic- https://mccormick.cx/news/entries/the-zero-customer-heuristic- When you're building some MVP or SLC it's tempting to over-think technical choices early on. It's tempting to build in all kinds of features and infrastructure.

"People might want a PDF," you think to yourself, "so I'll also build and deploy a PDF rendering server!"

"I'll need a way to measure everything my ten million customers are doing at scale so I'll deploy this elastically scaling mega analytics doodad framework server and hook it up to every user action in my app!"

cricket.gif

It's much easier to imagine new features than it is to actually build them. Before you know it there is a vapourware castle shimmering on the horizon.

I've found a useful thought pattern to combat this type of over-engineering. For every technical question just think to yourself:

What will my zero customers think of this?

When you are building the first version of something there are literally zero users. Unless it is the core function of the thing you are building, making a PDF rendering server or deploying a giga-scale analytics system is not what you should be doing. Your zero users don't care about those things! Getting even one person to actually use your thing and tell you if it's good or not is what you should be doing. Talking to people and asking them what they need is what you should be doing.

Let's protect the most scarce resource of all: our own time. At the start of the project let's make a bee-line towards shipping the best possible implementation of the core function of the software so we can find out if its useful and good.

]]>
/tags/all Wed, 08 Apr 2020 06:27 GMT
Slingcode: Personal Computing Platform entries/slingcode-personal-computing-platform https://mccormick.cx/news/entries/slingcode-personal-computing-platform Recently I was teaching one of my kids a bit of web coding. This is way more complicated than it should be. There are so many moving parts - configuration, build systems, editors, hosting requirements, certificates etc. just to get a simple web app running. Why?

I thought back to when I was learning to code with my mother on our Apple IIe. The computer was ours. The code was ours. The data was ours.

fdf0e437a027e6f3f3622f67d65e0ef0.png

I thought back to shareware. First diskettes and then FTP. I thought about my first website and those first Multi User Dungeons. Nethack and newsgroups and bulletin board systems. Trading ware with friends. I remembered running Linux for the first time on a Pentium. The freedom and power of getting to do what you want with your computer and a network connection. How can we take our computers back?

I started thinking about what a personal computing platform would look like today. A platform that a kid could jump right into and start coding. A platform where a kid could build cool stuff without asking for permission. Systematic convex tinkering with computers. Where they could own their software, and their data, and their device.

A way to make, run, and share web applications without needing site hosting, SSL certificates etc. An app repository and a text editor in a single web app. A way to share apps peer-to-peer, directly between trusted friends, family, and associates.

Then I re-read this:

  • Personal computers – in the original visions of many personal computing pioneers (e.g. many members of the Homebrew Computer Club), the PC was intended as personal property – the owner would have total control (and understanding) of the software running on the PC, including the ability to copy bits on the PC at will. Software complexity, Internet connectivity, and unresolved incentive mismatches between software publishers and users (PC owners) have substantially eroded the reality of the personal computer as personal property.

This desire is instinctive and remains today. It manifests in consumer resistance when they discover unexpected dependence on and vulnerability to third parties in the devices they use.

Nick Szabo in Trusted Third Parties Are Security Holes.

This is the important thing. It has to be self sufficient. It has to work properly whilst depending as little as possible on third parties.

c02481139c1de01523209cad3767a3ff.png

Last Monday I started building this. It's called Slingcode. With it, you can write, run, and share your own web applications directly in the browser. You don't need any build system, hosting provider, SSL certificate, or any thing else. You don't even need an internet connection. Just a web browser and the single HTML file containing the Slingcode web app.

Stay tuned.

]]>
/tags/all Sun, 15 Mar 2020 12:23 GMT
The Forest Moons of Yendor entries/the-forest-moons-of-yendor https://mccormick.cx/news/entries/the-forest-moons-of-yendor Last week I entered the 7 Day Roguelike Challenge. I used the the px3d game engine with Blender and ClojureScript to build a game prototype.

Screenshot of the game

forest-moon-bear.gif

Screenshot of the game

Screenshot of the game

]]>
/tags/all Sat, 14 Mar 2020 05:17 GMT
Sub-Dunbar Software entries/sub-dunbar-software https://mccormick.cx/news/entries/sub-dunbar-software I've been thinking about this idea lately of software written for small groups of mutually-trusting humans, like families. I like to think of this type of software as "sub-Dunbar software", named after Dunbar's number:

suggested cognitive limit to the number of people with whom one can maintain stable social relationships

Sub-Dunbar software is any software that is optimized for groups of this scale and intimacy. It's cozy.

337754d47c018b8c5e0b1378e4791c9b.png

Photo by Dan Gold on Unsplash

Here's some reading related to this idea:

it would not surprise me if we moved away from "public square" online dynamics to "small intimate online dinner party" online dynamics.

Tommy Collison

I didn't know it at the time, but todoMini is an example of this type of software. I wrote it for my family, not for scale, and it won't change. I hope to build more sub-Dunbar software.

a146ba6154b224489a665333822b241a.png

Photo by Kelsey Chance on Unsplash

]]>
/tags/all Wed, 19 Feb 2020 03:16 GMT
Linux Conf AU 2020 Talks entries/linux-conf-au-2020-talks https://mccormick.cx/news/entries/linux-conf-au-2020-talks Last week I was on the Gold Coast for Linux Conf AU. I gave two talks:

Piku: git push deployments to your own servers

px3d: a free software browser based pixel 3D nano-engine in ClojureScript

Other talks

I've compiled a list of talks I got a lot of value from which you can find on Twitter.

gold-coast.jpg

Enjoy!

]]>
/tags/all Wed, 22 Jan 2020 03:12 GMT
The Only Truth Will Be Cryptographic entries/the-only-truth-will-be-cryptographic https://mccormick.cx/news/entries/the-only-truth-will-be-cryptographic Photographs are easy to fake. So much so that there is a turn of phrase to describe it. People say something is "'shopped" when they are skeptical regarding the veracity of an image. This refers to the image editing program Photoshop which is often used to modify images. For example the magazine industry routinely modifies the photographs which appear in their pages.

8c1849c1304d48d92e635e494b919288.png

Recorded audio is even easier to fake.

In recent years we're discovering that even video, with its high information bandwidth, is easy to fake via carefully trained neural networks.

Seth Godin notes that anybody now has the ability to generate photos of completely fake people:

it's worth confirming the source before you believe what you see

-- Seth Godin

Where does this leave us? How do we reliably confirm the source? Physical reality prevents us from receiving most information first-hand. If most information that is not first-hand can be faked how can we ensure authenticity?

Cryptographic signatures will save us

The answer is good old cryptography.

One way to authenticate media with a high degree of certainty is to have it cryptographically signed. This provides a level of reputational consistency. If a president signs every speech they make with a certain cryptographic key then you have a way to check that the president who gave the first speech very likely is the same president gave the latest one.

Much more so than relying on indications of fakeness from the item of media itself. Additionally, the slightest alteration of the media will render the signature invalid and everybody will be able to see that it has been tampered with.

How far away are we from public figures cryptographically signing their statements? From people's phones signing the photographs they take? From organisations routinely signing blog posts, tweets, and everything they output into the world?

]]>
/tags/all Wed, 08 Jan 2020 03:20 GMT
Some Recent Sketches entries/some-recent-sketches https://mccormick.cx/news/entries/some-recent-sketches 1.jpg trees-1.jpg 2.jpg trees-2.jpg 3.jpg

]]>
/tags/all Tue, 31 Dec 2019 01:18 GMT
A Clojure-like Lisp That Runs In Bash entries/a-clojure-like-lisp-that-runs-in-bash https://mccormick.cx/news/entries/a-clojure-like-lisp-that-runs-in-bash wordmark.svg screencast.svg

Fleck is a Clojure-like LISP that runs wherever Bash is. Get it here.

This is a little experiment I hacked together from the amazing make-a-lisp project. My hard drive is littered with attempts to make this exact thing several times, and this was the first time I got it working properly.

My friend Crispin reminded me of this idea when we were discussing lightweight Clojure based scripting tools for domain specific tasks. He has made a fantastic Clojure static site generator called bootleg on top of GraalVM. We've both been inspired by the work of Michiel Borkent such as Small Clojure Intepereter and Babashka.

For more Clojure-likes check out awesome-clojure-likes.

Exciting times in the Clojure-verse!

]]>
/tags/all Sat, 30 Nov 2019 07:25 GMT
A Week in Singapore entries/a-week-in-singapore https://mccormick.cx/news/entries/a-week-in-singapore singapore.jpg cyber.jpg asimov.jpg veg1.jpg map.jpg watch.jpg

Last week I was in Singapore with my friends PVI Collective, consulting with local artists Ekamatra and Drama Box. We were hacking on eachother's artworks in their studio loft above Chinatown. Locative code, smart watches and dark LARP-arts - it was like I'd stepped into a post-cyberpunk William Gibson novel somehow.

I also gave a talk at the Singapore JS meetup on Bugout, the library I wrote to help build decentralized web applications. You can watch the talk here:

https://www.youtube.com/watch?v=zfPX-dR6LeM

This trip gave me the headspace to think about priorities and progress on my side projects. At the moment I'm focused on two main things apart from paid work.

Firstly, I'm working towards the launch of the paid version of SVG Flipbook, an SVG animation utility for people using Inkscape and Illustrator. Hopefully this will be ready for launch in the next couple of weeks. This is a project I started a while ago and I'm coming back to now.

Secondly, I had some time to think about and work on WebRTC Signaling Mesh, which is coming along nicely. It will be a way to do WebRTC signaling without resorting to centralized services. I've had the design floating around my head for a long time and I've finally begun implementation. Will hopefully have an update with progress on that soon.

Thanks for tuning in!

]]>
/tags/all Thu, 21 Nov 2019 01:35 GMT
Announcing The Bugout Box entries/announcing-the-bugout-box https://mccormick.cx/news/entries/announcing-the-bugout-box A few weeks ago at the BSides Perth conference I announced this piece of hardware I've been tinkering on.

The Bugout Box is a decentralized web appliance. Its a Raspberry Pi that any browser can connect to from anywhere in the world over WebRTC - a censorship-resistant way to serve data, APIs and apps.

Examples of things you can do with the box:

Bugout Box 3d view

Head to bugout.network/box if you want to find out more and sign up to the pre-release list.

]]>
/tags/all Tue, 05 Nov 2019 02:29 GMT
How I built an Excel add-in to export HTML tables entries/how-i-built-an-excel-add-in-to-export-html-tables https://mccormick.cx/news/entries/how-i-built-an-excel-add-in-to-export-html-tables An economist told me the worst part of her job is turning Excel data into HTML tables, so I built an add-in to fix it.

Many software developers probably don't realise that Microsoft Office add-ins these days are simply web pages which run inside of a panel in the UI. I suppose it was to be expected given the trend of the last couple of decades towards web based everything, but it still came as a surprise to me when I had to build one for a job. In my mind Office was still back in the world of COM objects and Delphi and DLLs.

After discovering how easy it is, I decided to do it again for this side project. Here is how I did it.

The plugin (whoops, "add-in") I built is called "Excel to HTML table" and does what it says on the tin. You make a selection of cells, click "copy", and you get a clipboard full of the corresponding HTML code that will render those cells. After that you can paste the code into your text editor and use it in a site's HTML.

Excel to HTML table screencast

Microsoft have some nodejs and yeoman projects to help you get up and running but I'm the sort of developer who likes to roll their own and keep things tight, tidy, and tiny. I like to build things from first principles so that I can understand what is going on at as low a level as possible. Here's what I discovered.

The Microsoft Tutorial has a lot of good info in it, but it's geared towards people using either Node + Yeoman or Visual Studio. If you're a text-editor-and-command-line person like me the best way to get started is just to grab the example .html, .css, and .js snippets from the Visual Studio version.

Get a dev server running

Any local HTTPS server will do. The difficult part is it must be HTTPS, even on localhost, or Word/Excel will refuse to load your add-in. My add-in is almost entirely client-side code and so I could get away with a small Python server using the built-in libraries like this:

from http.server import HTTPServer,SimpleHTTPRequestHandler httpd = HTTPServer(address, SimpleHTTPRequestHandler) httpd.socket = ssl.wrap_socket(httpd.socket, keyfile='selfsigned.key', certfile='selfsigned.cer', server_side=True) httpd.serve_forever()

As you can see this looks for the .key and .cer files in the local dir, and you can create those with openssl:

openssl req -x509 -newkey rsa:4096 -keyout selfsigned.key -out selfsigned.cer -days 365 -nodes -subj "/C=US/ST=NY/O=localhost/OU=Localhost"

You can do the same thing in nodejs with express if that's your bag by passing the right options to https.createServer:

const server = https.createServer({ key: fs.readFileSync('./selfsigned.key'), cert: fs.readFileSync('./selfsigned.pem') }, app).listen(8000)

Before you can load your add-in you should make sure your browser has accepted the self-signed cert or the add-in won't load. Do this by browsing to your localhost:8000 server and bypassing the certificate warnings.

If you're debugging in the native Office app instead of Office Online, you will need to do this in Internet Explorer for it to work as far as I can tell.

Manifest validation

add-in.gif

Office finds out about the URL for your add-in using a "manifest" file. This file is XML and pretty fragile. You need to make sure it complies with the spec. Luckily Microsoft have a tool for verifying the add-in on the command line. Install the npm package "office-toolbox": "0.2.1" and then you can run a command like this:

./node_modules/.bin/office-toolbox validate -m ./path/to/manifest.xml

This will report most issues that come up.

In the manifest you can use https://localhost:8000/Home.html and it will point to your local dev server when running.

The Code

The code itself is basically web code. You can use libraries like React and jQuery in your UI. The exception of course is when calling the native APIs. These are exposed through an interface like Excel.run(...) and make heavy use of promises for async. You will often find yourself doing context.load() and then waiting for the promise to resolve before doing the next thing in the document. The API documentation is super useful for figuring out what is possible and how to do it.

Debugging

When it comes to iterating on your code and debugging, by far the easiest way is to use Office Online. This is because it is already in the web browser so debugging works the same way as you are used to - the add-in is just an iframe.

I was even able to do my dev & debugging right at home in Firefox on GNU/Linux!

At some point you will want to debug using an actual copy of Office running on Windows. I used Office 2016 on my wife's computer.

If you are not a Windows developer the following tip will save you a lot of time when it comes to debugging native. It's called "F12 Developer Tools" and it's buried deep inside a Windows subdirectory in C:\Windows\System32\F12\.

What this tool does is attach a "web console" type of debugger to your Office add-in instance running inside Office. You can do stuff like console.log and also see JavaScript errors which are thrown.

Ready to roll

8e9a6781754c869c84202121fb7a2fef.png

Once you've got those pieces in place you should have a basic add-in up and running. After that it's all about referencing the docs to figure out how to do the thing you want your add-in to do.

Submit

Finally when you're ready to ship, you submit your manifest.xml to the App Source "seller dashboard". Expect to wait a few days for the validation team to get back to you, and to have to fix things. This process was useful as they see the software with a fresh pair of eyes and give actionable feedback.

LISP madness

So finally I should mention my LISP obsession. I actually wrote all the code for this plugin in a LISP called Wisp. It's a Clojure-like that compiles to JavaScript and seems quite similar to ClojureScript but with three core differences:

  1. It lacks almost all of the great features & tooling of ClojureScript.
  2. It is closer to being "JavaScript with LISP syntax".
  3. It compiles down very small if you know what you are doing. How small? My final compiled .js bundle for this add-in is just 8.2k non-gzipped.

So that's about it. I hope you got something out of this article on building Microsoft Office plugins using web tech.

Now back to open source dev for me. :)

]]>
/tags/all Mon, 30 Sep 2019 12:25 GMT
How To Make Hy-lang More User-Friendly entries/how-to-make-hy-lang-more-user-friendly https://mccormick.cx/news/entries/how-to-make-hy-lang-more-user-friendly Hy(lang) is a LISP-family programming language built on top of Python. You get the rich Python language & library ecosystem, with a LISP syntax and many of the language conveniences of Clojure, such as reader macros for easy access to built in data types. What's not to love?

97bb1a75b5a52e8c6a0a8e7e68cd4b4f.png

I recently found out I have the most GitHub stars for projects written in Hy of any developer worldwide. With this admittedly ridiculous credential in hand I'd like to offer some opinions on the language.

I really like Hy a lot. I prefer writing code in Hy to writing code in Python, and I am writing this post because I want to see Hy do well. I originally wrote this as a list of things in Hy that could possibly be improved, from the perspective of an end user such as myself. Then my friend Crispin sent me this fantastic video by Adam Harvey: "What PHP learned from Python" and I realised that all of the issues I have with Hy stem from the same basic problem that Adam talks about.

Here is the crux of the problem: I've written a bunch of software in Hy over the past few years and often when I moved to a newer point-release of the language all my old code breaks. My hard drive is littered with projects which only run under a specific sub-version of Hy.

gilch[m] It might depend on the Hy version you're using.

I understand that the maintainers of the language, who are hard-working people doing a public service for which I'm deeply grateful, are concerned with making the language pure and clean and good. I understand that languages have to change to get better and they need to "move fast and break things" sometimes. That's all fine and good.

However, I think Adam Harvey's point stands. If you want users to use and love your language:

  • Break things cautiously
  • Maintain terrible things if it makes life better
  • Expand the zone of overlap [backwards compatibility between consecutive versions]

I think if you can maintain backwards compatability you should.

Almost all of the breakages I've experienced between Hy versions could have been avoided with aliasing and documentation. Not doing this backwards compatibility work basically tells your users "don't build things with this language, we don't care about you." I know that is almost certainly not the attitude of the maintainers (who are lovely, helpful people in my experience) but it is the way it comes across as an end user.

Here is a list of things which have changed between versions which blew up my Hy codebases each time I upgraded Hy:

  • Renaming true, false, and nil to the Python natives True, False, and None. These could have been aliased to maintain backwards compabitility.

  • Removing list-comp and dict-comp in favour of the excellent lfor, dfor, and friends. Again, small macros could have aliased the old versions to the new versions.

  • Removing def in favour of setv. A very small macro or maybe even an alias could have retained def as it is pretty much functionally identical from the perspective of an end user.

  • Removing apply, presumably in favour of #**. Support could have been retained for the functional apply. There are situations where a proper apply is favourable.

  • Removing the core macro let. It seems there was an issue where let would not behave correctly with generators and Python's yield statement. A more user-friendly solution than chopping the imperfect let from the default namespace and breaking everybody's code would have been to document the issue clearly for users and leave the imperfect let in. I know it is available in contrib. Moving it to contrib broke codebases.

Having your old code break each time you use a new version is frustrating. It makes it hard to justify using the language for new projects because the maintainance burden will be hy-er (sorry heh).

Some other minor nits I should mention which I think would vastly improve the language:

  • loop/recur should be core. Like let these are available in contrib, but that means you have to explicitly import them. Additionally it would be super nice if they were updated to support the vector-of-pairs declaration style of let and cond etc.

  • Why does assoc return None? This is completely unexpected. If there are performance issues then create an alternative which does what Python dict assignment does (aset?) but it seems unwise to break user expectations so fundamentally.

36583050842532df644f183cc62890e7.png

It is much easier to provide criticism than it is to write working code. I am sorry this is a blog post instead of a pull request, and I hope this criticism is seen as constructive. I want to thank everybody who has worked on hy. I am a huge fan of the language. It is an amazing piece of software and I am very grateful for your work.

]]>
/tags/all Thu, 26 Sep 2019 13:20 GMT
Speaking schedule 2019 and beyond entries/speaking-schedule-2019-and-beyond https://mccormick.cx/news/entries/speaking-schedule-2019-and-beyond I've got three conference talks coming up in Perth (Australia), London, and The Gold Coast (Australia). If you're nearby let me know - I would love to buy you a coffee/beer and hear what you're up to.

Security BSides

This Sunday, September 22nd, Perth, Western Australia.

I'm presenting "Bugout: practical decentralization on the modern web." It's a talk about the library I built on top of WebTorrent for building web based decentralized systems.

Bsides Perth Logo

Clojure eXchange

December 2nd-3rd, London, UK.

I'm giving a keynote: a show and tell of the multitude of strange things I've been building with the Clojure[Script] family of programming languages, and how Clojure enables the bad habit of starting way too many projects. I'll also give an update on Thumbelina, the tiny MIDI controller I've been working on with my friend Dimity.

Skills Matter Clojure eXchange

Linux.conf.au

January 13th-17th, Gold Coast, Australia.

I'm talking about Piku, and how it helps you do git push deployments to your own servers. I've made a bunch of contributions to this open source project in recent months. I've personally found a huge productivity gain from being able to deploy internet services without having to think too much, and I'm excited to show others this too.

Linux Australia Logo

]]>
/tags/all Wed, 18 Sep 2019 07:50 GMT
Droneship Study entries/droneship-study https://mccormick.cx/news/entries/droneship-study a9bbb77a696a84276d8e05ed6a6867b1.png

Droneship. Study in the style of thisnorthernboy.

]]>
/tags/all Sun, 08 Sep 2019 06:54 GMT
Notes on "History of the Blockchain" by Nick Szabo entries/notes-on-history-of-the-blockchain-by-nick-szabo https://mccormick.cx/news/entries/notes-on-history-of-the-blockchain-by-nick-szabo In November 2015 Nick Szabo gave a talk on the history of the blockchain which was dense with useful ideas.

61120c710d06cf0ec63488c1abd14665.png

Here are some notes I took on his talk:

  • Philosophical inspiration to Cypherpunks who invented Cryptocurrency:

    • Ayn Rand: Galt's Gulch - independence from corrupt institutions.
    • Tim May: "protect yourself with cryptography" (cyber Galt's Gulch.)
    • Friederich Hayek: Institutions of property, contracts, money are actually important to human freedom.
  • Use computer science to minimize vulnerability to strangers.

  • Non-violently enforce the good services of institutions.

  • "Try to secure as much as possible" not just communication.

  • Cryptography: only secures communications from 3rd parties.

  • David Chaum: let's apply this to money too.

  • Centralization problem remained in digital cash startups.

  • Bad assumptions in computer security: trusted third parties like certificate authorities are secure.

  • Trusted third parties are security holes.

  • Centralization is insecure.

  • E.g. Communists were able to get stranglehold with just control of railroads, newspapers, radio.

  • Gold is insecure

    • Spanish looted Aztec gold, pirates looted Spanish gold.
    • Part of end of gold standard was German U-boat threat to British gold transportation.
    • Franklin Roosevelt's government confiscated gold.
    • In modern times xray machines detect gold easily.
  • Decentralization per computer science is much more automated & secure than traditional security.

  • CS decentralization can only replace small fraction of traditional security but with very high cost savings.

  • Traditional security isn't the protocol itself, requires strong external law enforcement.

  • Computer security can be secure across national borders instead of siloed inside jurisdictions.

  • Cryptocurrency helps solve this through decentralization.

  • Separation of duties: several independent people to perform a task to get it done.

  • Each node as independent as possible.

  • E.g. crude measure of independence: geographic diversity of nodes.

  • Number of nodes is only a proxy measure of decentralization.

  • Smart contract:

    • Long lived process or "distributed app".
    • Acts like a contract.
    • Performance, verification etc.
    • Generally 2 parties + blockchain (replacing TPP).
  • Wet code = traditional law. Dry code = smart contract.

  • Law is subjective, enforced with coercion, flexible, highly evolved.

  • Smart contracts are mathematically rigorous, cryptographically enforced, rigid, very new.

  • Law is jurisdicionally siloed, and expensive to execute.

  • Smart contracts are super-national & independent and low cost.

  • Seals in clay/wax were important when writing was invented: signature + tamper evident.

  • Modern seals at e.g. crime scenes: sealing door, evidence bag with numeric identifier.

  • Blockchain can keep secure log with both semantics (serial number) and proof of evidence (photo hash).

  • Put proof of evidence on blockchain as well as semantic reference for contract code to interface with.

  • Can secure physical spaces with same mechanism.

  • Proplets: blockchain can tell them which keys have which capabilities.

    • For almost any valuable property that can be controlled digitally
    • Example: Auto-repo collateral upon contract breach.
    • Example: creditors without access to offshore oil rig used as collateral.
  • Recent project:

    • Trust minimized token: secure property titles, colored coins. Securing transfer of ownership.
    • Trust minimized cash flows (dividends, coupons, etc).
  • Idea: social networks for blockchains. Execute payment swaps & smart contracts after linking social accounts together.

  • Let's try to think about security more broadly instead of only encryption.

  • Let's try to protect everything that's important to us, without centralization.

]]>
/tags/all Wed, 28 Aug 2019 14:09 GMT
ClojureScript Pixel Game Engine With Blender Live-reloading entries/clojurescript-pixel-game-engine-with-blender-live-reloading https://mccormick.cx/news/entries/clojurescript-pixel-game-engine-with-blender-live-reloading Recently I've been hacking on a game engine for infinitelives called px3d.

around.gif

It's built on top of ClojureScript, Blender, and Three.js and it runs in the browser.

One feature I'm particularly happy with is the live-reloading of Blender assets into the game. You hit "save" in Blender and the updates appear in the running game a second later - no need to re-compile or re-load the game.

live-reloading.gif

The way this works is with a background script which watches the assets.blend file. It re-builds the assets.glb whenever it is modified, and writes the hash of the file into assets.cljs. Figwheel pushes changes to the compiled cljs files whenever they change, and there is another bit of code which tells three.js to re-load assets.glb if the hash has changed.

Infinitelives

Infinitelives is the vehicle me and my buddy Crispin use to make games and tooling, mostly for gamejams. The gamejam format is great because it is time-boxed, which means we can periodically do this self-indulgent thing we enjoy without taking too much family or work time.

Gamejams are typically only 48 hours long and so we have learned some good techniques for shipping working code under extreme constraints. A hardcore economy of time, resources, and scope is required.

ClojureScript & Figwheel are perfect for this with their hot-loading of modified code. I built the tight Blender re-load integration for the same reason. Hand drawn graphics consume a lot of time during jams and this should help us really level up on the content side of things.

cda02a53bdeea5ee13ac8d943761a42b.png

If you'd like to find out when we release games and new tools you can sign up to our release notifications on the infinitelives home page or follow us on Twitter.

If you liked this you might also like my Roguelike game web template which you can get on itch.io. Thanks for supporting my work!

]]>
/tags/all Sat, 17 Aug 2019 05:35 GMT
Goomalling entries/goomalling https://mccormick.cx/news/entries/goomalling Image1802377562.jpg Image530038439.jpg Image1309211065.jpg Image-95081980.jpg Image-206257949.jpg Image-1571672220.jpg Image1085423041.jpg Image531224935.jpg Image-651670289.jpg Image-1045430088.jpg

]]>
/tags/all Sun, 04 Aug 2019 09:06 GMT
Joplin With Self-hosted Sync entries/joplin-with-self-hosted-sync https://mccormick.cx/news/entries/joplin-with-self-hosted-sync For some time I have been looking for a writing solution with the following properties:

  • Lets me review and make minor edits on my phone.
  • Is synched to my laptop where I can write longer form.
  • Supports simple markup such as Markdown.
  • Supports attachments and images.
  • Is Free & Open Source Software and can be self-hosted.

The solution is Joplin.

screenshot.png

It's a wonderful piece of software. There are apps for all of the usual platforms, including a direct link to the Android apk, which is a blessing if you are somebody who opts out of using Google services.

553e4b7b90a90a9132db5b59f75c84b3.png

Some other things which are great about Joplin:

  • You can edit notes in an external editor.
  • You can paste images directly into your document.
  • It imports and exports many formats including Markdown.
  • It can export individual articles to PDF.
  • Its native export format "JEX" is a simple tar file.
  • Its native data store is on-disk.
  • Sync is optional and very easy to set up.
  • Sync uses the widely supported webdav protocol.

Joplin Sync

I got sync between my devices working quickly by using Piku to deploy a simple webdav server to my Piku VPS. If you want to do this yourself, check out the latter repository and then push it to Piku as follows:

git remote add piku piku@MYSERVER.NET:webdav
git push piku master
piku config:set NGINX_SERVER_NAME=WEBDAV.SOMEDOMAIN.NET PASSWORDS="username:password username2:password2 ..." FOLDERS="/joplin:/home/piku/joplin"

After that is up and running you can configure Joplin sync by selecting "webdav" on each device and then enter the URL WEBDAV.SOMEDOMAIN.NET/joplin/ and the username:password pair you specified above.

I wrote this post with Joplin.

]]>
/tags/all Thu, 01 Aug 2019 03:19 GMT
Hacksilver: technical details entries/hacksilver-technical-details https://mccormick.cx/news/entries/hacksilver-technical-details setup-thumbnail.jpg

Yesterday I released Hacksilver, an album of procedurally generated "algorave" music. Some people had questions about the technology used to write it so I thought I'd write this up.

The beats and melodies were generated using drillbit, a LISP codebase written in a Python variant called Hy. The project outputs Impulse Tracker mod files which are then played and mixed live.

The interesting parts of that codebase are in the generators folder. For example the drill-n-bass choppage generator is here.

Each generator has three functions:

  • make-sample-set: which generates IT wav tables that are used by the generator (e.g. individual drum kit or synth sounds)
  • make-pattern-settings: which sets up parameters & context that will be re-used by the pattern generator to provide similarity across pattern variations
  • make-pattern: which outputs the pattern data in a format easily consumed by the Impulse Tracker file writer

Mixing and live-effects are performed in Pure Data. Originally I was using a fully software based mixer. However I discovered that a nicer mode of operation is to have individual bits of sound generating/filter hardware chained together. So I started using this Raspberry Pi based mixer + FX unit from another project to mix live.

One other bit of software in there is jsfxr which is wrapped by the LISP code and outputs 8-bit synth sounds (which are then used by the pattern generator). Because the synth definitions are simple JSON hash maps there is a fun pseudo-evolutionary technique I was able to use where you interpolate between the values of two synth definitions to generate new sounds based on two synth definitions that you like.

Hardhat tracker module 
player

I also built a little hardware Impulse Tracker renderer based on a Raspberry Pi running XMP with my friend Dimity. It has a Pocket Operator style sync output and runs directly into the mixer that both share the same timing and the fx can be quantised to the music which is playing.

If you're interested in the music hardware that Dimity and I are building and selling you can stay updated at bzzt.studio.

In the image at the top of this post the hardware Impulse Tracker renderer is the little box on the right hand side. The RPi mixer/fx unit is to the top right of the C64 keyboard. The Korg Nanokontrol2 strapped to the C64 keyboard is controlling the fx and mixing parameters on the RPi. They keyboard itself was for playing live synth sounds (a very simple arpeggiating subtractive synthesizer built in Pd).

]]>
/tags/all Fri, 07 Jun 2019 02:24 GMT
Hacksilver: new algorave album entries/hacksilver-new-algorave-album https://mccormick.cx/news/entries/hacksilver-new-algorave-album HackSilver 💀 ⚔ by chr15m

I just released Hacksilver, a new album of procedurally generated music.

It uses a whole slew of weird tech to generate the beats, melodies, synth sounds including beat-generating LISP, 8-bit synth generating Javascript, and Pure Data for the mixing and mastering. One thing that was particularly fun was procedurally generating Impulse Tracker files.

Would appreciate a re-share if you know of anybody who might be into this type of thing.

Enjoy!

]]>
/tags/all Thu, 06 Jun 2019 06:58 GMT
BEP44 For Decentralized Applications entries/bep44-for-decentralized-applications https://mccormick.cx/news/entries/bep44-for-decentralized-applications Depiction of decentralized network

In 2014 Arvid Norberg and Steven Siloti came up with a BitTorrent extension called BEP44. The basic purpose of BEP44 is to allow people to store small pieces of information in a part of the BitTorrent network called the DHT. The DHT ("distributed hash table") is a key/value lookup table that is highly decentralized. Prior to BEP44 it was used to look up the IP addresses of peers from the hashes of the torrent they were sharing.

BEP44 introduces two new ways of storing key-value data in the DHT. The first is the ability to look up small bits of information keyed directly on their hash. The second allows for the storage of cryptographically authenticated data, keyed on the public key. What this means is if you have some public key K then you can look up authenticated blobs of data stored in the DHT by the owner of that key. This opens up a variety of useful abilities to people building decentralized applications.

For instance when you distribute a file on BitTorrent it is immutable - you can't change it - but BEP44 provides a way to tell people "hey, there is a new version of this file that I shared before which you can find over here" in a secure and authenticated way. It turns out that this basic mechanism can be used to build a wide variety of decentralized, authenticated functionality and people have built cool experiements like a decentralized microblog using it.

The purpose of this post is to show you how the datastructure works and how to apply it more widely in your own software.

If you just want a working implementation you can use, check out the decentral-utils JavaScript library which has a single-function implementation of the datastructure and algorithm which you can use in the browser. Web browsers unfortunately do not have direct access to the BitTorrent DHT, but the library is still useful for authenticating up-to-date data blobs that are passed around between browsers, for example over WebRTC.

What's good about BEP44

Most of the advantage of BEP44 comes from the cryptographic signing. What this offers is a way for somebody (let's call her Alice) to verify that a piece of data from somebody else (let's call him Bob) is authentic and that it has not been tampered with. You can do cryptographic signing without BEP44 of course.

However, the BEP44 specification brings some other features for building decentralized systems:

  • Namespacing against a public key.
  • Replay attack prevention.
  • A compare-and-swap primitive.

The namespacing feature means that a single public key can have a bunch of different data blobs that others can authenticate. Because the datastructure is keyed on both the public key and a "salt" field, a single public key can store multiple blobs with different "salt" values as a sub-key.

Replay attack prevention is accomplished with the sequence field, which in BEP44 is a monotonically increasing integer. The way a replay attack works is some adversary keeps an old copy of something you have signed and then when you are offline they send it again and pretend its a new message. Because the message is signed with your key people are fooled by the adversary into thinking the old data is current. In BEP44 the sequence field prevents this because the adversary will have a data blob with a lower sequence number than the last one you shared and they can't generate a new blob with a higher sequence number because they do not have your private key. So the replayed data blob will be rejected.

Finally, the compare-and-swap primitive works by specifying a previous sequence number that the current data blob should replace. Peers will reject data blobs which don't replace the current sequence number they hold. In this way it's possible to acheive basic synchronisation and atomicity of data blobs. Using compare-and-swap guarantees that the new value you want to insert into the DHT can be calculated based on up-to-date information.

Example usage

Imagine you take upon yourself the small task of building a decentralized social network. Users have a timeline of posts which they have written. When they write a new post it should be appended to their timeline and people should see the updated timeline with the new post.

In the centralized social network case this is easy. The social network provider TwitFace has an internal copy of the poster's timeline to which they add the new post. Followers are notified of the update and the updated timeline is sent to them by the provider. The way the authentication works in this case is the original poster has logged in with their password and so the provider knows who they are, but the receivers of the update must trust the provider.

Depiction of centralized post authentication

How do readers know that the post is authentic and comes from the original poster? The reader must trust the provider completely. They must trust that the provider is showing them the authentic timeline of messages, that the messages have not been modified, that fake messages have not been injected into the timeline, that new messages have been added to the feed in a timely fashion, and that no message has been censored and removed from the feed by the provider.

The problem with this model is that trusted third parties are security holes. Centralized social networks do modify things people have said. They do inject posts into people's timelines (ads and worse). They do change the order and timing of posts to suit their own goals. Perhaps worst of all, they do censor posts completely.

I won't get into the politics of censorship. Suffice it to say that censorship is fine and good right up until the point where your values differ from the entity doing the censoring. Values change, mysterious outside influences are myriad, and few people have complete alignment of values even with our noble corporate overlords in Silicon Valley.

The basic goal here is that if you've chosen to read somebody's timeline you can trust that the feed of posts you are reading from them is authentic and complete and as they intended.

Happily, we can use the BEP44 technique to route around the trusted-third-party centralized-social-network-provider security hole. BEP44 provides a way to receive an update and verify it cryptographically. It provides a way to know that this is the latest and complete version. It allows the verifier to do this using only the received data structure without requiring any kind of secret server side logic or password based authentiction like you would find in a centralized social network.

Depiction of decentralized post authentication

How it works

At the heart of the algorithm is a small datastructure which can be shared, updated by the sharer, and then shared again. Receivers can verify the updates are authentic. The idea is that you can share a small piece of authenticated data which points to a larger piece of immutable data. For instance, you can share an authenticated datastructure with the hash of a torrent containing all of your posts (an RSS feed for instance). Receivers then know that torrent is the current representation of your timeline of posts.

The datastructure has the following fields:

  • value
  • seq
  • cas [optiona]
  • salt [optional]
  • pubkey
  • signature

When a verifier receives a copy of this data structure they perform the following checks:

  • Is the size of the value field below the maximum size?
  • Does the value conform to the expected format / encoding?
  • Is the seq number higher than the last seq number I saw (if any)?
  • If cas is present does it match the previous seq number I have?
  • Is the attached signature made with the private key corresponding to the attached pubkey over the datastructure's value, seq, and salt fields?

In this final point they are checking that the structure has been digitally signed with the author's private key. This is accomplished by concatenating salt + seq + value and then checking that the signature is valid for that data and the given public key.

In this way verifiers can know that an update from a known keypair/identity is authentic and current.

Read more posts on the subject of cryptography & decentralized systems.

]]>
/tags/all Sun, 12 May 2019 03:28 GMT
Sally Kathryn (Laing) McCormick entries/sally-kathryn-laing-mccormick https://mccormick.cx/news/entries/sally-kathryn-laing-mccormick SALLY.jpg

My mother, Sally Kathryn (Laing) McCormick, passed away on the 25th of April, 2019. This is the eulogy I gave at the celebration of her life on Monday.

"Your mum is the nicest person I've ever met."

I've heard these words from friends and strangers countless times during my life. They are how I slowly came to understand that Mum was somebody extraordinary and not to be taken for granted. I'm so glad to have been able to express my own gratitude to her in recent years.

In Henry the Fourth Shakespeare has a turn of phrase: "like bright metal on sullen ground." Mum was bright metal on sullen ground. She was solid gold.

Everyone here will have their own fond, and if you knew her well, sometimes frustrating memories of my mum. I want to focus today on those things I came to really admire in her.

We all knew that warm side of her because she gave it freely, to everyone, everywhere she went. No matter who you were you would get a sweet smile, warmth, and praise. Perhaps fewer people here will know of her strength, energy, and willpower, her humour, her infinite gratitude, and her fundamentally uncomplaining nature.

In recent years I've found inspiration in the philosophy of the stoics and it is hard not to notice strong parallels with the way mum carried herself, always ready to help, always grateful, always giving. Standing straight, or held straight, by sheer will. Until around 11 each night when the universe would gently say "ok, it's time to sleep now Sally", and she would nod off where she stood.

And then, as my father knows all too well, was woken every morning by her two alarms, one at 4:30, so that she had "time to think", and one at 5 so that she could actually wake up.

But Mum was not a stoic, she was a Christian. Even though she was tested by the Good Lord with three opinionated, and two atheist sons, her faith never waivered in the slightest, even at the very end. As with everything about her, it held fast and true and uncompromising throughout her life. I am not a Christian but I admire her persistence greatly.

I am going to try to be more like my mother.

I'm going to try to be as grateful as she was. To remember that every morning I wake, every cup of coffee, every lego spaceship built with my kids, is a gift of limited supply. To delight and give thanks as she did in the smallest of things, common or uncommon. Any time I said thank you to mum, she would brush it off. Clearly, she thought it her basic duty to this world and the people in it to give everything she had with gratitude.

The most valuable thing each of us has to give is our time. Mum always had time for people and most especially for us her sons. In this too I hope to emulate her and give more time to my own kids, and to you, my friends and family, and the other people in my life, just as she did.

She never ever complained. Never. Instead she did whatever was in her control to help other people, and sometimes even things which were well outside her control. She knew that complaining accomplishes nothing but to make you feel worse and to burden those around. Mum never burdened anybody. If I need help I will ask for it. If I can fix something I will simply fix it, just as she did.

Mum had incredible energy. It wasn't the kind of energy you get from an energy drink, or from eating high energy food, because we all know she did not do that. No, it was the energy that springs forth at 4am at the hospital after several sleepless nights when your sick kid needs to be held. Its the energy you didn't realise you had until you decided to try and overcome the fatigue with willpower. Mum made amazing use of that particular reserve, and whilst it would probably not be advisable to dip in to quite the same extent as she did, it's good to know that there is always more you can give when required.

Finally, I aspire to mum's strength. She powered joyfully 100% into everything that life handed to her, and even her last hugs were vice-like, and with a genuine smile. Again that is not because she was particularly physically strong, although she was, but because she was emotionally and spiritually strong. If a mouse like my mother can be so mighty, surely I can too. She made it apparent that it is a simple matter of choosing to be strong.

Mum and Dad's legacy speaks for itself. The life they made for themselves in this country. The lives they helped others to build. The success of their sons Dirk and Mike in building a good life for themselves, sometimes against extraordinary odds which most people will never face. Of course I also owe a huge debt to my parents for the wonderful life I enjoy today, a debt I intend to pay forward wherever I can, in my mum's spirit.

At times like this the universe can seem cold, and harsh, and unfair; but people like Mum show us that goodness and love abound in this universe. The goodness and love is in us. We get to defy the cold and the unfair. Like mum, each of us gets to choose to be strong, to love, and to make reality wonderful for each other.

Thank you Mum, for caring for me, and Dirk, and Mike, and Dad, and for Orson and Scout, and for always believing in me. Thank you for always laughing out loud at our stupid jokes. Thank you for sitting patiently and learning to code with me on our Apple IIe when I was 8. Thank you for showing me the way to be a good human. I promise I will try.

]]>
/tags/all Fri, 10 May 2019 09:55 GMT
Build a decentralized web chat in 15 minutes entries/build-decentralized-web-chat-15-mins https://mccormick.cx/news/entries/build-decentralized-web-chat-15-mins This post originally appeared on the David Walsh blog.

In this 15 minute tutorial we're going to build a simple decentralized chat application which runs entirely in a web browser.

All you will need is a text editor, a web browser, and a basic knowledge of how to save HTML files and open them in the browser.

Diagram of how WebRTC works browser to browser

We're going to use Bugout, a JavaScript library that takes care of the peer-to-peer networking and cryptography.

Get the full tutorial on GitHub

Read more posts on the subject of cryptography & decentralized systems.

]]>
/tags/all Wed, 27 Mar 2019 13:13 GMT
TOPLAP 15th Birthday Streamed Algorave entries/toplap-15th-birthday-streamed-algorave https://mccormick.cx/news/entries/toplap-15th-birthday-streamed-algorave As part of the TOPLAP 15th Birthday live-stream I live-coded some algorithmic rave music in Speccy:

Speccy is a browser based environment for live-coding 8-bit algorithmic rave music in ClojureScript.

You can watch the videos of everybody who participated here.

]]>
/tags/all Fri, 22 Feb 2019 09:05 GMT
Global Game Jam 2019: Otoch entries/global-game-jam-2019-otoch https://mccormick.cx/news/entries/global-game-jam-2019-otoch album-cover.png

A couple of weekends ago my friend Crispin and I made this game as part of Global Game Jam, an event in which participants build a game in 48 hours.

It was a lot of fun and I got to spend most of the time drawing and doing graphics and music, which was a nice break from writing software.

Play it online!

]]>
/tags/all Mon, 04 Feb 2019 07:39 GMT
Catskills, NY, USA entries/catskills-ny-usa https://mccormick.cx/news/entries/catskills-ny-usa Image1774727300.jpg

So long 2018.

]]>
/tags/all Sat, 29 Dec 2018 16:39 GMT