More from Alex Meub
Using modern AI coding tools feels like jumping into the cockpit of a BattleMech. My co-worker used this analogy recently and I love it. It perfectly sums up the feeling of vibe coding for me. I can move faster, jump higher and it feels like a there is a whole new world of possibilities available to me. This is true for me as someone who no longer writes code every day, but many experienced software engineers don’t feel this way and I get it. They’ve been running around on foot and learned to be very effective without it. Jumping into the cockpit of something entirely new is jarring. The BattleMech can feel clunky, burdensome and they basically have to relearn all their instincts around movement and orientation (it can also sometimes shoot itself in the foot!). On top of this, many non-technical folks have also jumped into the BattleMech. They are running off in all these strange directions because they don’t know what to use it for. They are copying things, building things that suck and filling social media feeds with their creations. Many software engineers feel the same way as artists did a few years ago because pretty much anyone can create software now. The good news is that domain knowledge and software development instincts are still essential. The BattleMech can be incredible if you know exactly where you want it to go, but it’s also happy to lead you straight off a cliff.
Progress Quest is generally considered the original idle game. It came out in 2002 as a parody of EverQuest and the emerging MMORPG boom. “Playing” it consists of creating a character, clicking “Sold!”, and then watching progress bars fill forever. There’s no interaction, no real gameplay, just waiting. At first glance, it feels like a gimmick — a joke game built to poke fun at the MMO trend. But what’s surprising is that its creator, Eric Fredricksen, built a whole RPG simulation underneath the progress bars. There are 270+ monsters, procedurally named equipment, multiple storylines, intentionally weighted stats, real loot tables, and a surprisingly complex progression system. On top of that, there’s even an authentication system for competitive multiplayer leaderboards that are somehow still around today. I love Progress Quest because of its absurdity, but also because it’s such a good example of something being far better than it needed to be. The amount of effort and attention to detail in this game makes me smile. Exponential Progression At the heart of Progress Quest is a single formula that controls level progression. The time it takes to complete level N is: (20 + 1.15^N) * 60 seconds. That means early levels take minutes (a few hours to get to level 10), while later ones take years (many years to get to level 100). There is also additional time outside of leveling for the player to go to market, buy/sell things, and head back to the “killing fields”. There are still thousands of players active across the remaining multiplayer realms, and pretty much everyone above level 95 has had the game running for over a decade. That doesn’t even count single-player characters. Races and Classes The race and class systems are hilariously absurd, but they don’t affect gameplay at all. The player races are: Half Orc, Half Man, Half Halfling, Double Hobbit, Hob-Hobbit, Low Elf, Dung Elf, Talking Pony, Gyrognome, Lesser Dwarf, Crested Dwarf, Eel Man, Panda Man, Trans-Kobold, Enchanted Motorcycle, Will o’ the Wisp, Battle-Finch, Double Wookiee, Skraeling, Demicanadian, and Land Squid. And the classes are: Ur-Paladin, Voodoo Princess, Robot Monk, Mu-Fu Monk, Mage Illusioner, Shiv-Knight, Inner Mason, Fighter/Organist, Puma Burgular, Runeloremaster, Hunter Strangler, Battle-Felon, Tickle-Mimic, Slow Poisoner, Bastard Lunatic, Jungle Clown, Birdrider, and Vermineer. Hilarious Monster Types There are over 270 hand-crafted monsters, and every one of them has a thematic loot drop. The Giant series imagines what giants would be if they were made of basically anything: Humidity Giant (drops “drops”) Beef Giant (drops “steak”) Rice Giant (drops “grain”) Porcelain Giant (drops “fixture”) Mini Giant (drops “pompadour”) The Golem series follows the same logic: Beer Golem (drops “foam”) Oxygen Golem (drops “platelet”) Cardboard Golem (drops “recycling”) The Scout hierarchy is great: Cub Scout (drops “neckerchief”) Girl Scout (drops “cookie”) Boy Scout (drops “merit badge”) Eagle Scout (drops “merit badge”) The Elemental series is an entirely new take on Elementals: Bacon Elemental (drops “bit”) Cheese Elemental (drops “curd”) Hair Elemental (drops “follicle”) Porn Elemental (drops “lube”) When the game picks a monster to fight, it level-matches against your character and then applies modifier prefixes based on the gap. That adds more flavor to the monster names. Procedural Equipment When you get new gear, the game doesn’t just pull from a list. It runs a little algorithm: Pick a base item matched to your level from a list like Stick → Shiv → Longsword → Halberd Calculate the quality gap between the item’s base level and your level “Spend” that gap across up to two modifier adjectives, each with a point value Whatever is left becomes a numeric +N prefix So a level 40 character might find a +13 Custom Holy Mithril Mail — a level 19 Mithril Mail base, with Custom (+3) and Holy (+5), leaving +13 unspent. The modifier tables are split into good and bad. If the gap is negative — meaning the item is actually better than you — the game pulls from the bad list instead: Rusty, Dull, Bent, Plastic, Nerf (-7), Rubber (-6). There’s also a whole list of spells with names like “Holy Batpole,” “Grognor’s Big Day Off”, and “Roger’s Grand Illusion” that will make their way into your spellbook and increase in level with roman numerals. The Main Game Loop Surprisingly, the game has a real game loop. It works like this: Kill monster task — The game generates a monster with a duration based on your level. When the timer finishes: Loot is added to your inventory, either a specific drop or generic loot XP is gained, which can trigger a level-up Quest and plot bars advance Check encumbrance — After a kill, if encumberance is at or above your limit, you go to market instead of fighting: The game will say “heading to market to sell loot” Then the game sells items one at a time, removing the top item in inventory and adding gold Items with “of” in the name sell for much more This continues until only gold remains Buy or head out — After selling, or if you weren’t encumbered in the first place: If the player’s gold is high enough to buy better gear, the game says “Negotiating purchase of better equipment” and the game upgrades a random equipment slot Otherwise the game will say “Heading to the killing fields” Next kill — After heading out, the game generates another monster and the cycle repeats Stat Progression When you gain a stat point, the game uses a weighted system biased toward your highest stat. Half the time, the gain is completely random. The other half uses quadratic weighting, where each stat’s chance is proportional to its value squared. That creates a snowball effect where your best stat keeps getting better, which feels authentic to how RPG builds tend to work. The only real strategy to playing Progress Quest is trying to roll high STR at character creation. Having higher STR gives you higher max encumberance which affects how often you need to go to market. The thing is, the market trips are such a small fraction of actual game time that this only about a 5% difference in how fast your character will level up. In Conclusion This is probably more than anyone wanted to know about Progress Quest. It’s an absurd, charming little game, and I hope it somehow keeps living forever. If you want to play the “multiplayer” Windows version, you can download it here. I also wanted to play on my Mac, so I vibe-coded an Swift version that runs on modern Mac hardware. See you on the killing fields!
I made a retro-inspired dock to charge my Playdate out of a Raspberry Pi case. The case is a miniature version of the Super Famicom and I love how it makes the Playdate look like a little game cartridge when it’s charging. Making one yourself is pretty straight-forward, you’ll just need the following components: Retroflag SUPERPi case A compact right-angle USB-C cable, like this one The 3D printed insert I designed First, print the two halves that make up the 3D printed insert and attach them with super glue. Make sure to align the cutouts on each side and then clamp the two pieces in place while the glue dries. Then take the Retroflag case apart and unscrew the main board. You’ll have to cut some wires and remove the front-facing USB ports. Make sure to leave the rear-facing USB-C port in place as we’ll reuse this to power the Playdate. Then, using a Dremel, cut a rough 86 by 21 mm rectangular hole in the top of the case. It doesn’t have to be clean as it will get covered up by the 3D printed insert. You will also need to remove some of the internal support structure inside the case using pliers or flush cutters to make space for the insert and wires. After it has dried, insert the 3D printed piece through the hole and hot glue the right-angle USB-C cable into place. Lastly, splice the USB-C cable to the red and black wires coming off the rear-facing USB port on the case. The red (or pink) USB-C cable wire should be spliced to the red wire on the USBC-C port. The two black wires should also be spliced together. That’s it! At some point I’d like to make it into a functional USB hub and add an internal LED.
A few years ago, I built a Wi-Fi-controlled Nerf turret, but I never got around to creating a proper build guide for it. When I finally sat down to write one, I realized just how many things I would do differently. That realization quickly snowballed into a full redesign—and the result is a vastly improved version of the SwarmTurret. This new version is not only more powerful and precise, but it’s also easier build. Here are some of the major upgrades: Simplified Assembly: I reused the shell of an existing plastic blaster, which significantly cuts down on 3D printing and makes putting it together much easier. Improved Stability: A new belt-driven Y-axis adds smoother motion and includes an adjustable tension system. Enhanced Accuracy: The camera has been repositioned for better aiming precision. Direct X-Axis Drive: I replaced the original gear system with direct motor control for more responsive movement. Performance Boost: Upgraded from Raspberry Pi 4 to Pi 5 for faster web app performance. Integrated Power Supply: Now features a built-in power supply with an on/off switch—no more fumbling with cables. Web App Enhancements: The control interface is more intuitive and responsive. I’ve published the complete build guide, along with the updated code and 3D printable parts.
3D Printing has allowed me to be creative in ways I never thought possible. It has allowed me to create products that provide real value, products that didn’t exist before I designed them. On top of that, it’s satisfied my desire to ship products, even if the end-user is just me. Another great thing is how quickly 3D printing provides value. If I see a problem, I can design and print a solution that works in just a few hours. Even if I’m the only one who benefits, that’s enough. But sharing these creations takes the experience even further. When I see others use or improve on something I’ve made, it makes the process feel so much more worthwhile. It gives me the same feeling of fulfillment when I ship software products at work. Before mass-market 3D printing, creators would need to navigate the complexity and high costs of mass-production methods (like injection molding) even to get a limited run of a niche product produced. With 3D printing, they can transfer the cost of production to others. Millions of people have access to good 3D printers now (at home, work, school, libraries, maker spaces), which means almost anyone can replicate a design. Having a universal format for sharing 3D designs dramatically lowers the effort that goes into sharing them. Creators can share their design as an STL file, which describes the surface geometry of their 3D object as thousands of little triangles. This “standard currency” of the 3D printing world is often all that is required to precisely replicate a design. This dramatically lowers the effort that goes into sharing printable designs. The widespread availability of 3D printers and the universal format for sharing 3D designs has allowed 3D-printed products to not only exist but thrive in maker communities. This is the magic of 3D printing: it empowers individuals to solve their own problems by designing solutions while enabling others to reproduce those designs at minimal cost and effort.
More in programming
I listen to a lot of podcasts, and I like how they fit around other tasks. I press play, lock my phone, and put it down. I’m free to wash the dishes, fold the laundry, or shop for groceries. Unfortunately, more and more information is only published as a video. Technical talks, conference sessions, video essays – they don’t work in an audio-only podcast app. I could convert these videos to MP3 files, but that breaks down the moment a video isn’t pure spoken word. If a speaker says, “Look at this slide” or holds up a diagram, an audio-only file leaves me stranded. I don’t want to give up the podcast player I like, nor stare at a screen for an hour – but I do want the information in these videos. To solve this, I’m abusing my podcast player’s chapter support. This gives me the best of both worlds: I can listen to a video as audio-first, and glance at my lock screen if I need a moment of visual context. The idea: Chapters every few seconds MP3 files can have ID3 metadata, and ID3 metadata can include chapters. A chapter covers a particular time range, and it can have an associated title, description, and cover art. My podcast app of choice is Overcast, which can’t play videos, but it does have robust chapter support. I can jump between chapters, navigate a table of contents, and see per-chapter cover art. To get videos into Overcast, I’m creating MP3 files with a new chapter every few seconds, and the per-chapter cover art is a corresponding frame from the video. As I play the file, I get a slow, stop-motion-like rendition of the original video. If my phone is locked, I can glance at my lock screen and see the current frame in the Now Playing screen. Overcast is developed by Marco Arment, and I got this idea from Forecast, his app for adding chapters to podcasts. In particular, I was struck by its ability to create chapters that don’t display in the chapter list – ideal if I don’t want a table of contents with hundreds of entries. As I was developing my script, I compared my output to the output from Forecast to ensure I was creating the chapters correctly. The code: FFmpeg and Mutagen There are three steps in this process: Convert a video file to an MP3 Extract images from the video at a fixed interval Insert the images as hidden chapters in the MP3 file Let’s go through each in turn. 1. Convert a video file to an MP3 Converting a video file to an MP3 is a single FFmpeg command: ffmpeg -i video.mp4 audio.mp3 This is consistently the slowest step of the process, and I do wonder if I could use different settings or an alternative encoder to make it go faster – but it’s not slow enough to be worth further investigation. 2. Extract images from the video at a fixed interval Extracting images from a video needs a more complicated FFmpeg command: ffmpeg -i video.mp4 \ -vf 'fps=1/5,scale=iw*sar:ih,scale=min(iw\,945):min(ih\,945):force_original_aspect_ratio=decrease' \ thumbnail_%04d.jpg This extracts an image every 5 seconds, downscales any image larger than 945 pixels square (while preserving the original aspect ratio), and saves the results as sequentially numbered JPEG images (thumbnail_0001.png, thumbnail_0002.png, and so on). The key is the -vf flag, which defines two FFmpeg filters: The fps filter selects one frame every 5 seconds (fps=1/5). The first scale filter scales the width based on the sample aspect ratio (scale=iw*sar:ih). Without this filter, frames can be stretched and distorted. The second scale filter scales the input video, preserving the original aspect ratio (force_original_aspect_ratio=decrease), and ensuring the output images fit within 945×945px or the size of the input video, whichever is smaller. My limit is 945 pixels because that’s the largest size that cover art is shown on my iPhone. This filter still isn’t completely correct – it sometimes creates images from portrait videos that are smaller than I’m expecting – but it’s good enough. These are only thumbnails for glancing at, and if I want to change it later, I can always do the image resizing outside FFmpeg. 3. Insert the images as hidden chapters in the MP3 file Inserting the chapters into the MP3 file is more complicated. Although FFmpeg has basic support for ID3 metadata, as far as I know, it can’t insert chapters with per-chapter artwork. Instead, I’m going to reach for Python and the Mutagen library. Here’s the code to add a chapter to an MP3 file: from mutagen.id3 import APIC, CHAP, ID3, PictureType audio = ID3("audio.mp3") with open("thumbnail_0001.jpg", "rb") as f: img_data = f.read() image_frame = APIC(mime="image/jpeg", type=PictureType.OTHER, data=img_data) chapter_frame = CHAP( element_id="chp1", start_time=0, end_time=5 * 1000, sub_frames=[image_frame] ) audio.add(chapter_frame) audio.save() This creates a single chapter that lasts the first 5 seconds (0 to 5000 milliseconds), and the per-chapter cover art is thumbnail_0001.jpg. If we ran this in a loop, we could add images for every 5 second slice of the original video. This code is inserting two frames into the ID3 metadata: The CHAP (chapter) frame contains the timing information, and it can have subframes for metadata like title, chapter art, or associated URL. The APIC (attached picture) subframe contains information about a picture, which can either be a blob of image data or a URL to an image on the web. Normally, you’d also insert a CTOC frame which defines a table of contents, but I don’t want a TOC with hundreds of 5-second chapters, so I’m deliberately not doing this here. This is allowed by the ID3 spec – you’re not required to insert a CTOC frame if you’re using chapters, and you can have chapters that aren’t listed in your table of contents. To work out which frames I needed, I used Forecast to create some chapters by hand, and I inspected their frames. In particular, loading an MP3 and calling Mutagen’s pprint() method shows a human-readable list of frames, and then I could drill into the individual fields: from mutagen.id3 import ID3 audio = ID3("audio.mp3") print(audio.pprint()) I wrapped all this code in a project called glancecast, which allows you to convert a video file with a single command, with optional flags to set the frame length and chapter art size: $ python3 glancecast.py interesting_talk.mp4 interesting_talk.mp3 The process takes a minute or so to complete, most of which is spent transcoding the video file to MP3. The resulting MP3s are usually 40 to 50 MB in size, which is very reasonable. The outcome: How it looks in practice Here’s what one of these “glanceable” podcasts looks like in Overcast and on my lock screen: Maggie Appleton presented this talk over two years ago and it’s been on my “talks to watch” list ever since. Once I put it in Overcast? I listened to it in less than a day. It’s not a lot of extra information, but enough that I can quickly glance down and get the gist of what a speaker is saying. Both views update with a new frame every few seconds, or I can put my phone in my pocket and ignore the screen. I’ve used this approach for half a dozen videos so far, and I’m happy with the results. I expect to keep using it, because I have a long queue of videos I’ve been meaning to watch. If you’d like to try this, check out glancecast for the full code and instructions. [If the formatting of this post looks odd in your feed reader, visit the original article]
Andrew Baker, the current Group CIO at Capitec Bank wrote an interesting piece on AI and open source, and how these tools that generate code according to one’s specification may replace the general reliance on open source implementations done by contributors around the world. I’d really recommend reading it. I have great admiration and respectContinue reading "AI Isn’t Replacing Open Source"
A framework for thinking about when AI involvement is additive or a violation
Why we need richer, thicker interfaces and better boundary objects for collaborative planning with agents
A look at 10 foundational pillars that enable agents to operate more competently and more efficiently in any codebase.