[{"content":"The Battle of the Atlantic, 1941-43. One player commands the escorts of a transatlantic convoy, and the other directs the U-boats hunting it. Neither can see the whole battle. Each side only knows what its own sensors report: hydrophone bearings, ASDIC fixes, and sightings from lookouts and periscopes. So the two players have very different pictures of the same fight.\nThis is my entry for Slow-mo Gamejam 2, where the theme is Squad Goals. Working out what is where, and then doing something about it, takes several vessels working together. It\u0026rsquo;s also my first attempt at both a real-time strategy game and a deliberately asymmetric two-player design, so I may have bitten off more than I can chew.\nVery early days # This is a work in progress, and more of a sensor toy than a game so far. There are no weapons yet and no way to win or lose, and you can\u0026rsquo;t give your ships new orders. Online two-player is the goal, but the networking comes later. For now, a CPU commander can run either side.\nWhat\u0026rsquo;s in so far:\nSeparate views: each side has its own chart, built only from what it has detected. Watching CPU v CPU shows both charts side by side. Sensors with real limits: a hydrophone has a blind arc astern, lookouts can only spot a periscope that\u0026rsquo;s raised, and a corvette\u0026rsquo;s ASDIC ping tells the submarine where the ping came from. Uncertain tracks: repeated contacts build an estimate of position and course, drawn as an ellipse that grows and fades unless new contacts sharpen it. Ships sailing close together can be hard to tell apart, which is exactly what I want. Captains: each captain follows standing orders and asks permission before going beyond them. That\u0026rsquo;s what the Y and N keys are for. Time warp: pause, and speed up or slow down with the - and + buttons. How to try it # Choose New Match, then Change Scenario to pick one, then choose which side to command or Watch CPU v CPU.\nSensor Trial is the one to watch: a U-boat keeps pace beside an escorted merchant, so every sensor has something to report. Captain Trial shows the captains asking permission. Answer them with Y or N. First Contact is the start of the real game: one corvette, one freighter, one U-boat, and each side trying to find the other. The typeface is X Typewriter by GGBotNet.\n","date":"4 November 2026","externalUrl":null,"permalink":"/games/convoy/","section":"Games","summary":"","title":"Convoy","type":"games"},{"content":"","date":"4 November 2026","externalUrl":null,"permalink":"/tags/gamejam/","section":"Tags","summary":"","title":"Gamejam","type":"tags"},{"content":"","date":"4 November 2026","externalUrl":null,"permalink":"/games/","section":"Games","summary":"","title":"Games","type":"games"},{"content":"Welcome to my corner of the internet. This is the place where I share the things I make and do. It is purely intended for the mild amusement of myself and my mates but you\u0026rsquo;re welcome to stay and look around!\n","date":"4 November 2026","externalUrl":null,"permalink":"/","section":"Magic Monkey Nuts","summary":"","title":"Magic Monkey Nuts","type":"page"},{"content":"","date":"4 November 2026","externalUrl":null,"permalink":"/tags/naval/","section":"Tags","summary":"","title":"Naval","type":"tags"},{"content":"","date":"4 November 2026","externalUrl":null,"permalink":"/tags/strategy/","section":"Tags","summary":"","title":"Strategy","type":"tags"},{"content":"","date":"4 November 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"Small, achievable steps. Claim one in your Discord devlog with some evidence and claim: plus its number or name, e.g. claim: 12.\n","date":"27 October 2026","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/achievements/","section":"Jams","summary":"","title":"Achievements","type":"jams"},{"content":"","date":"27 October 2026","externalUrl":null,"permalink":"/jams/","section":"Jams","summary":"","title":"Jams","type":"jams"},{"content":"Totals so far. Ties share a place, and nobody is taking this seriously.\n","date":"27 October 2026","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/leaderboard/","section":"Jams","summary":"","title":"Leaderboard","type":"jams"},{"content":"","date":"27 October 2026","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/","section":"Jams","summary":"","title":"Players","type":"jams"},{"content":" Slow-mo Gamejam 2 - Nov 26 Sun 1 Nov – Mon 30 Nov 2026 Theme: Squad Goals Week 2 of 4 Home Rules Achievements Leaderboard Players Game jams are great, but they don\u0026rsquo;t fit very well around busy grown-up life. This is the slow-mo version: a whole month, a shared theme, and a group of friends cheering each other on. There is some gentle competition, but this is not about grinding, perfection or winning anything.\nThe goal: steady progress, shared often, and help each other finish more things.\nDates # Sunday 1 November – Monday 30 November 2026 Work at your own pace. There are no daily requirements. Weeks run Sunday to Saturday. Claims made on 29–30 November count towards week 4. Theme: Squad Goals # Interpret it however you like:\nA team, crew, party, flock or swarm A goal that only works when things cooperate Sport (five-a-side counts) Aspirations, ambitions, or someone\u0026rsquo;s very specific life goals Make sure you claim On Theme by explaining your take on it.\nWhat counts as a project? # Anything game-adjacent:\nGames, toys, experiments, interactive tools Any engine, framework or language, or even paper prototypes Solo or collaborative (just be clear what you worked on) Achievements and points # There are lots of small achievements, and each can be earned once. Most should take about 1–2 hours or less. 5 pts: very small progress (30–60 min) 10 pts: solid, focused work 15 pts: a meaningful milestone or iteration 20 pts: harder, integrative, or very shareable progress Points are for fun, bragging rights and momentum only.\nHow to claim # Do the thing. Post some evidence in your devlog thread in Discord: a screenshot, a clip, a build, or a few sentences. Write claim: followed by the achievement number or name, e.g. claim: 12 or claim: Bug Hunter. Several in one post is fine: claim: 1 and claim: 4. It appears on the leaderboard with the next update, greyed out until the referee confirms it. Shared goal # Everyone\u0026rsquo;s points also count towards one shared target. If the group reaches it by the end of the month, the popadoms reward is unlocked! Helping someone else earn points helps everyone.\nShared goal 220 / 750 pts If the group hits 750: Edward is buying the popadoms at the next Coding Curry Night!\nWeekly mini-twists # Each Sunday a new optional mini-twist is revealed: a light constraint, a creative lens, or a nudge to share. Taking part earns a bonus achievement. They appear on the jam home page and the achievements page as they are revealed.\nWeekly roundups # Every week this site publishes a roundup of everyone\u0026rsquo;s progress, built from your Discord devlog posts. If there\u0026rsquo;s something you don\u0026rsquo;t want quoted on the public site, react to it with 🙈 and it will be left out.\nSharing # Sharing is the heart of the jam. Good things to share:\nScreenshots or short clips Before and after comparisons A few sentences about what changed or what you learned Code snippets or system explanations WIP is encouraged. Rough is good. Broken is fine.\nFinishing # You do not need a perfect or complete game. A playable build, a toy, or a clear ending is a win. At the end of the month, share what you made, reflect on what worked and what didn\u0026rsquo;t, and celebrate finishing something.\n","date":"27 October 2026","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/rules/","section":"Jams","summary":"","title":"Rules","type":"jams"},{"content":"A month of making small games together, slowly. No all-nighters, no crunch: just steady progress, shared often, with a few silly points along the way.\nThe theme is Squad Goals. Take it wherever you like: a team of characters, a crowd that moves as one, a goal you can only reach together, five-a-side football, or a game about your friends\u0026rsquo; questionable life ambitions.\nRead the rules, browse the achievements, and post your progress in Discord.\n","date":"27 October 2026","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/","section":"Jams","summary":"","title":"Slow-mo Gamejam 2 - Nov 26","type":"jams"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/posts/","section":"Blog Posts","summary":"","title":"Blog Posts","type":"posts"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/tags/claude/","section":"Tags","summary":"","title":"Claude","type":"tags"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/tags/tutorial/","section":"Tags","summary":"","title":"Tutorial","type":"tags"},{"content":" The problem # I use one PC for two very different kinds of work. During the day it\u0026rsquo;s my work machine, with company repos, a company Claude account and the company\u0026rsquo;s usage plan. In the evenings and at weekends it\u0026rsquo;s where Gremlin, the games on this site and this blog get made, on my own personal Claude account.\nOut of the box, Claude Code doesn\u0026rsquo;t know that distinction exists. Every claude session on the machine reads and writes the same configuration directory, so there\u0026rsquo;s one login, one set of settings, one history and one set of memory. Whoever logged in most recently wins.\nOut of the box: two kinds of work, one identity That causes some real problems:\nThe wrong account gets used. Forget to switch and your hobby project runs on the work plan, or work code goes through a personal account. Neither is great, and the second could break your employer\u0026rsquo;s policies. Switching is slow. Logging out and back in each time breaks your flow, and you can\u0026rsquo;t run both at once. Settings and memory get mixed. I want different defaults for each (different models, different permission modes). Claude\u0026rsquo;s memory of my personal projects has no business turning up in a work session, and the reverse is true too. What I want is two commands, claude-work and claude-personal, that always run as the right account, can run side by side, and keep everything apart. I also want both as one-click options in VS Code\u0026rsquo;s terminal menu.\nThis guide covers Linux and WSL (Windows Subsystem for Linux), which is where I run Claude Code. The VS Code part assumes VS Code on Windows with WSL terminals, and there\u0026rsquo;s a note for anyone using the Remote-WSL extension instead.\nHow it works # The whole solution depends on one environment variable: CLAUDE_CONFIG_DIR.\nWhen Claude Code starts, it checks that variable to decide where its state lives. If it isn\u0026rsquo;t set, Claude uses the default ~/.claude/ directory and a ~/.claude.json file in your home folder. If it is set, everything goes into the directory you named instead: the login credentials, .claude.json, settings.json, history and per-project memory.\nSo \u0026ldquo;two accounts\u0026rdquo; really means \u0026ldquo;two config directories\u0026rdquo;. You run the same claude program with a different value of CLAUDE_CONFIG_DIR each time, and it behaves like two separate installs.\nOne binary, two config directories, chosen by an environment variable The rest of this guide just makes that easy and reliable to use.\nStep 1: Create the wrapper commands # Create two small shell scripts in ~/.local/bin, which is the usual place on Linux for commands that belong to you rather than to the system:\nmkdir -p ~/.local/bin ~/.local/bin/claude-work:\n#!/bin/sh # Launch Claude Code with the \u0026#39;work\u0026#39; account\u0026#39;s config directory. # A real command (not a bash alias) so it works from any shell, script, # or the VS Code terminal. CLAUDE_CONFIG_DIR=\u0026#34;$HOME/.claude-work\u0026#34; exec \u0026#34;$HOME/.local/bin/claude\u0026#34; \u0026#34;$@\u0026#34; ~/.local/bin/claude-personal:\n#!/bin/sh # Launch Claude Code with the \u0026#39;personal\u0026#39; account\u0026#39;s config directory. CLAUDE_CONFIG_DIR=\u0026#34;$HOME/.claude-personal\u0026#34; exec \u0026#34;$HOME/.local/bin/claude\u0026#34; \u0026#34;$@\u0026#34; Then make them executable:\nchmod +x ~/.local/bin/claude-work ~/.local/bin/claude-personal Each script is only one line, but every part of that line does a job:\n#!/bin/sh is the shebang. When you run a file directly, the kernel reads this first line to find out which interpreter should run it. I\u0026rsquo;ve used plain sh rather than bash because the script needs no bash features, and sh starts up slightly faster. CLAUDE_CONFIG_DIR=\u0026quot;…\u0026quot; command puts the variable assignment on the same line as the command. That sets the variable only for that one command. Nothing gets exported into your shell, so a plain claude typed afterwards is unaffected. exec replaces the shell script\u0026rsquo;s process with claude instead of starting claude as a child and waiting for it. No idle sh process sits in between, so claude receives keyboard signals (like Ctrl+C) directly, and its exit code becomes the command\u0026rsquo;s exit code. \u0026quot;$HOME/.local/bin/claude\u0026quot; is the full path to the real binary. The native installer puts it here (as a symlink to the current version). Using the full path means the wrapper can never accidentally call itself or some other claude that happens to be earlier on your PATH. Run command -v claude to check where yours lives, and change the path if it\u0026rsquo;s somewhere else. \u0026quot;$@\u0026quot; passes on any arguments you give the wrapper, exactly as you typed them, so claude-work --continue or claude-personal -p \u0026quot;explain this\u0026quot; work just as they would with claude. You don\u0026rsquo;t need to create ~/.claude-work or ~/.claude-personal yourself, because Claude Code creates the directory the first time it starts with it.\nWhy not just use an alias? # My first version was two lines in ~/.bashrc:\nalias claude-work=\u0026#39;CLAUDE_CONFIG_DIR=~/.claude-work claude\u0026#39; It worked fine in my own terminal, and nowhere else. Aliases are a feature of the interactive shell. They only exist in a shell that has read your .bashrc, and other programs can\u0026rsquo;t see them at all. Scripts, bash -c \u0026quot;…\u0026quot;, VS Code tasks and anything that launches a process directly will all fail with \u0026ldquo;command not found\u0026rdquo;. A script in a directory on your PATH is a real command, so any program can find and run it. That matters in Step 4.\nStep 2: Make sure ~/.local/bin is on your PATH # When you type a command, the shell looks for it in each directory listed in PATH, in order. On Ubuntu, ~/.profile already adds ~/.local/bin if it exists. However, ~/.profile is only read by login shells, and it only adds the directory if it existed at the time you logged in. To be safe, add this to the end of ~/.bashrc as well:\nexport PATH=\u0026#34;$HOME/.local/bin:$PATH\u0026#34; Putting it first means your own commands take priority over system ones with the same name. Open a new terminal (or run source ~/.bashrc) and check:\ncommand -v claude-work claude-personal Both should print a path inside ~/.local/bin.\nStep 3: Log in once per account # Now start each command once and log in with the matching account:\nclaude-work # then run /login and sign in with your work account claude-personal # then run /login and sign in with your personal account Each login is saved in that account\u0026rsquo;s own directory (on Linux, a .credentials.json file readable only by you), so you only do this once. Inside a session, /status shows which account and config directory you\u0026rsquo;re using. It\u0026rsquo;s worth checking the first few times until you trust it.\nFrom now on you can have a work session open in one terminal and a personal one in another, and neither will ever see the other\u0026rsquo;s login, history or memory.\nStep 4: Add the commands to VS Code\u0026rsquo;s terminal menu # Typing claude-work is easy, but I want both accounts in VS Code\u0026rsquo;s + ▾ terminal dropdown so a session is one click away. VS Code calls these entries terminal profiles, and you define them in your user settings.json. Open it with Ctrl+Shift+P and choose Preferences: Open User Settings (JSON).\nHere\u0026rsquo;s an example. Ubuntu is the default name for the WSL Ubuntu distro, but yours may differ, so run wsl -l -v in Windows to check and use that name in place of Ubuntu below:\n\u0026#34;terminal.integrated.profiles.windows\u0026#34;: { \u0026#34;Ubuntu (WSL)\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;C:\\\\WINDOWS\\\\System32\\\\wsl.exe\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-d\u0026#34;, \u0026#34;Ubuntu\u0026#34;], \u0026#34;icon\u0026#34;: \u0026#34;terminal-ubuntu\u0026#34; }, \u0026#34;Claude (work) (WSL)\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;C:\\\\WINDOWS\\\\System32\\\\wsl.exe\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-d\u0026#34;, \u0026#34;Ubuntu\u0026#34;, \u0026#34;-e\u0026#34;, \u0026#34;bash\u0026#34;, \u0026#34;-lic\u0026#34;, \u0026#34;claude-work\u0026#34;], \u0026#34;icon\u0026#34;: \u0026#34;hubot\u0026#34; }, \u0026#34;Claude (personal) (WSL)\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;C:\\\\WINDOWS\\\\System32\\\\wsl.exe\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-d\u0026#34;, \u0026#34;Ubuntu\u0026#34;, \u0026#34;-e\u0026#34;, \u0026#34;bash\u0026#34;, \u0026#34;-lic\u0026#34;, \u0026#34;claude-personal\u0026#34;], \u0026#34;icon\u0026#34;: \u0026#34;hubot\u0026#34; } }, \u0026#34;terminal.integrated.defaultProfile.windows\u0026#34;: \u0026#34;Ubuntu (WSL)\u0026#34; Save the file, and the two Claude entries appear in the dropdown next to the arrow beside + in the terminal panel. Here\u0026rsquo;s what happens when you pick one:\nFrom a menu click to a work-account session Each setting does the following:\nterminal.integrated.profiles.windows: VS Code itself runs on Windows here, so the profiles go under the Windows key. The terminal starts wsl.exe, and WSL takes over from there. -d Ubuntu picks the distro. It isn\u0026rsquo;t needed if you only have one, but it makes the profile explicit. -e bash -lic claude-work: -e tells WSL to run this program directly rather than opening your default login shell. The program is bash with three flags: -l makes it a login shell, and this is the flag that\u0026rsquo;s easy to miss. A login shell reads ~/.profile, which on Ubuntu adds ~/.local/bin to PATH. Without it, bash depends entirely on your .bashrc having the PATH line from Step 2, and if that\u0026rsquo;s missing, bash can\u0026rsquo;t find claude-work. -i makes the shell interactive. Ubuntu\u0026rsquo;s default ~/.bashrc starts with a guard that returns immediately unless the shell is interactive, so -i makes sure the rest of your .bashrc runs too, including any PATH changes you\u0026rsquo;ve made there. -c runs the following string as a command, then exits. icon sets the icon in the terminal tab. It takes any codicon name. hubot is a little robot head, which seemed appropriate. defaultProfile keeps an ordinary shell as the default for +, so I only get Claude when I ask for it. Because claude is the terminal\u0026rsquo;s process, the tab closes when you quit Claude. That\u0026rsquo;s what I want, but if you\u0026rsquo;d rather drop back to a shell afterwards, change the command to \u0026quot;claude-work; exec bash\u0026quot;.\nUsing the Remote-WSL extension? If VS Code is connected into WSL (the green \u0026ldquo;WSL: Ubuntu\u0026rdquo; badge in the bottom-left corner), the terminal already runs inside Linux. Use the Linux profile key instead, and you can drop wsl.exe altogether:\n\u0026#34;terminal.integrated.profiles.linux\u0026#34;: { \u0026#34;Claude (work)\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;bash\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-lic\u0026#34;, \u0026#34;claude-work\u0026#34;], \u0026#34;icon\u0026#34;: \u0026#34;hubot\u0026#34; } } Step 5 (optional): Give each account its own settings # Each config directory has its own settings.json, so you can now tune each account separately. A work account may, for example, use a different - probably more expensive! - default model and your personal one perhaps has a more hands-off permission mode, or is set up for long, high-effort sessions:\n~/.claude-work/settings.json:\n{ \u0026#34;model\u0026#34;: \u0026#34;fable[1m]\u0026#34; } ~/.claude-personal/settings.json:\n{ \u0026#34;model\u0026#34;: \u0026#34;opus\u0026#34;, \u0026#34;permissions\u0026#34;: { \u0026#34;defaultMode\u0026#34;: \u0026#34;auto\u0026#34; }, \u0026#34;effortLevel\u0026#34;: \u0026#34;high\u0026#34; } The [1m] on the end of the work model is a qualifier that selects that model\u0026rsquo;s 1 million token context window. The context window is how much the model can hold in mind at once: your conversation, the files it has read and the results of the commands it has run. A bigger window means longer sessions on large codebases before Claude has to summarise and drop older context. The trade-off is cost, because long contexts use up a plan\u0026rsquo;s allowance faster, and the 1M versions aren\u0026rsquo;t available on every plan. Leave the suffix off (just \u0026quot;model\u0026quot;: \u0026quot;fable\u0026quot;) to get the standard window.\nThe same goes for plugins, skills and per-project memory under projects/. Each account builds up its own, and they never get mixed.\nWhat is and isn\u0026rsquo;t separated # It helps to be clear about where the line falls:\nKept separate per account Shared by both Login and usage plan Your project files settings.json A repo\u0026rsquo;s CLAUDE.md and .claude/ folder History, sessions, memory The claude binary and its updates Plugins and skills you install Your shell, Git config and SSH keys Anything that lives in the repository is shared, which is usually what you want. It\u0026rsquo;s the project\u0026rsquo;s own instructions, so both accounts should follow them. Anything that lives in your home directory, outside the two config directories, is shared too. If you also need separate Git identities or SSH keys for work and personal repos, set those up separately (Git\u0026rsquo;s includeIf in ~/.gitconfig is a good place to start).\nFinally, plain claude still uses the default ~/.claude, which is effectively a third, unnamed account. I simply stopped using it. If you want to be strict, you could point plain claude at one of the two accounts, but I find typing the account name every time is the best guard against using the wrong one.\nSummary # Claude Code keeps all of its state in one directory, and CLAUDE_CONFIG_DIR lets you choose which one. Two one-line scripts in ~/.local/bin (claude-work and claude-personal) each set that variable and exec the real binary. ~/.local/bin goes on your PATH in .bashrc. Log in once with each command. Two VS Code terminal profiles run wsl.exe -e bash -lic claude-work (or claude-personal), so each account is one click away. That\u0026rsquo;s about ten lines of configuration in total, there\u0026rsquo;s nothing new to install, and I haven\u0026rsquo;t picked up the wrong account once since setting it up.\n","date":"26 October 2026","externalUrl":null,"permalink":"/posts/two-claude-accounts-one-pc/","section":"Blog Posts","summary":"","title":"Two Claude Accounts, One PC","type":"posts"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/tags/vscode/","section":"Tags","summary":"","title":"Vscode","type":"tags"},{"content":"","date":"26 October 2026","externalUrl":null,"permalink":"/tags/wsl/","section":"Tags","summary":"","title":"Wsl","type":"tags"},{"content":"Add game instructions, backstory, or development notes here.\n","date":"9 October 2026","externalUrl":null,"permalink":"/games/miniblaster/","section":"Games","summary":"","title":"MiniBlaster","type":"games"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/devlog/","section":"Tags","summary":"","title":"Devlog","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/embedded/","section":"Tags","summary":"","title":"Embedded","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/gremlin/","section":"Tags","summary":"","title":"Gremlin","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/series/gremlin-dev-log/","section":"Series","summary":"","title":"Gremlin Dev Log","type":"series"},{"content":" Look, no screen! # August took me back into my work thoughts and so took Gremlin to places with no screen at all - a Raspberry Pi Zero keeping an eye on my internet connection, and the microcontroller inside a CNC machine. And back in game land, Tanx finally made Gremlin answer a question every multiplayer game has to face sooner or later: who gets to decide what actually happened?\nHeadless Gremlin # A major pain for any thoughts I may have of \u0026lsquo;home automation\u0026rsquo; is the constant (monthly it seems) hanging of my household router - never really got to the bottom of it but the ol\u0026rsquo; \u0026rsquo;turn it off and on again\u0026rsquo; always resolves it. But the point of remote control is you\u0026rsquo;re remote! If I am there to physically restart the router, I might as well turn the heating on myself while I am at it!\nSo time to press Gremlin into the more prosaic task of relentlessly keeping my crappy router alive and connected.\nThere\u0026rsquo;s now a new build option for a headless Gremlin: just Core and Network, with no graphics or audio. Its first user is this little internet watchdog for the home network. It runs on a Pi Zero and regularly pokes a few plain-HTTP connectivity-check endpoints. If the internet looks dead for several checks in a row, it power-cycles the router through a GPIO-driven relay. There\u0026rsquo;s a small status page on the LAN, with a big button for a manual cycle.\nTo make that possible, Gremlin gained:\nHTTP client and server classes, now formally part of Gremlin and backed by ASIO. As with the game networking, ASIO stays out of the public headers. A GpioLine abstraction, with a Linux implementation and a \u0026rsquo;null\u0026rsquo; one for everywhere else, so the app still builds and runs on my desktop. A standard-library file reader to replace the SDL2-backed one, because a headless build has no SDL. (If I ever build for Android again I\u0026rsquo;ll need to revisit that. Future me\u0026rsquo;s problem.) This is the abstraction layers paying off in a direction I never expected. Because the platform stuff sits behind interfaces, \u0026lsquo;Gremlin without a window\u0026rsquo; turned out to be a CMake option and a few new implementations - not a fork. Nice.\nGoblin: talking to microcontrollers # The other big chunk of the month was CNCController, a replacement controller stack for my CNC machine based on grblHAL. Its architecture splits the job in two: a \u0026lsquo;host\u0026rsquo; on a Linux machine plans the moves, and a \u0026lsquo;device\u0026rsquo; - a dedicated microcontroller - executes them in real time. That means the link between the two has to be something I can really trust.\nThe answer is Goblin, a new, separate library that Gremlin now pulls in as a submodule. The design rules are strict:\nFreestanding C99 - no dynamic allocation, no blocking, no clock access. So exactly the same source compiles on the PC and on an AVR microcontroller, and there\u0026rsquo;s only one implementation of the wire format to get right. Its first protocol, Goblin Link, provides framing (COBS), CRC-16 checks, a versioned handshake, credit-based flow control and heartbeat supervision, over any ordered byte stream - a UART, TCP or a pty. Gremlin apps use it through a small C++ wrapper. By the end of the month the first proper stepping code was in, and a real stepper motor moved on the X axis. There\u0026rsquo;s something about software making a physical thing move that never gets old!\nWho decides? # Back in game land, Tanx\u0026rsquo;s multiplayer hit a classic problem. Player A destroys a tank, and player B\u0026rsquo;s next routine position update - sent before B knew - puts it straight back. The update isn\u0026rsquo;t corrupt, and it isn\u0026rsquo;t out of order. It\u0026rsquo;s just describing a world that no longer exists.\nSo NetAuthority.h adds three small pieces, deliberately independent of any transport, ECS or game:\nNetStamp / NetStampGate - continuous state (positions, orientations) belongs to its owner and the last writer wins, but every update is stamped with the tick and the object\u0026rsquo;s incarnation. When the host makes a decision about an object it bumps the incarnation, which voids every stale update still in flight. NetEventSequencer - discrete events (a hit, a death, a score) belong to the host, which numbers them. Clients never decide; they claim, and then apply the host\u0026rsquo;s ruling through the same code path the host uses. NetSessionClock - the shared, roughly-agreed timeline both of the above refer to. Or, to put it in one line: continuous state is replicated, discrete events are adjudicated. Anything that has to be agreed belongs in the second pile. It ships with its own test suite, and Tanx was using it to resolve state through the host by the 31st - the same day it got a minimap.\nBits \u0026amp; bobs # Child Transforms now compound world rotation and scale symmetrically, and Buccaneer\u0026rsquo;s particle firing now uses the new world rotations. Colour can be read and written as a hex string, which is now how colours get stored. UI parenting is consistent: no parent means \u0026rsquo;the scene root\u0026rsquo;, with a debug warning if neither can be found. You can grab the render system\u0026rsquo;s copy of UITheme to tweak it independently. Tanx moved over to the latest UI patterns as part of a bigger tidy-up. What\u0026rsquo;s next? # The authority model assumes both peers are running the same game with the same data - and at the moment nothing actually checks that. Need to get that nonsense sorted out before Tanx gets an online playtest\u0026hellip;\n","date":"31 August 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-08/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: August 2026 - No Screen Required","type":"gremlin"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/gremlin/","section":"Gremlin Engine","summary":"","title":"Gremlin Engine","type":"gremlin"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/multiplayer/","section":"Tags","summary":"","title":"Multiplayer","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/networking/","section":"Tags","summary":"","title":"Networking","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/tags/raspberry-pi/","section":"Tags","summary":"","title":"Raspberry-Pi","type":"tags"},{"content":"","date":"31 August 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"31 July 2026","externalUrl":null,"permalink":"/tags/cad/","section":"Tags","summary":"","title":"Cad","type":"tags"},{"content":"","date":"31 July 2026","externalUrl":null,"permalink":"/tags/ecs/","section":"Tags","summary":"","title":"Ecs","type":"tags"},{"content":" A month of two halves\u0026hellip; # July was split between work and a long-overdue summer break, so the coding split neatly in two. Work was still on my mind at first, but then I settled into my go-to turn-based wargame. The first week was a burst of CAD work - drawing tools and snapping. The rest of the month belonged to Skirmish, which moved its game objects over to ECS composition and, in doing so, dragged a couple of embarrassingly missing features into Gremlin.\nDrawing, with snapping # Gremlin\u0026rsquo;s Cad namespace gained a proper drawing-tool framework: a CadTool base, a CadToolStack that routes input to whichever tool is active, a first PolylineTool and TransformTool, and a little CursorRay helper for turning the mouse position into a ray in the 3D view. Within the day there was basic polyline drawing, with grid and axis snapping, running in the workbench.\nThe interesting part is snapping. The design splits policy from positions:\nSnap providers each know about one kind of position - grid points, axis lines, geometry already in the document and, after a rework the next day, geometry the active tool is generating. The SnapEngine owns the policy. It throws away disabled kinds, keeps candidates within tolerance, and ranks them by kind priority first, cursor distance second. That ranking rule is what makes it feel like CAD. If distance came first, a grid point right under the cursor would always beat an endpoint a few pixels away, and you\u0026rsquo;d never be able to hit an endpoint near a grid line. Maddening! Priorities are a table rather than code, so they can be tuned without touching the engine.\nTwo follow-ups landed within days. Snap radii are now correct at oblique viewing angles - a tolerance in pixels means very different distances in the world when you\u0026rsquo;re looking along a plane. And angular snapping draws little indicator lines, so you can see what you\u0026rsquo;ve locked on to.\nSkirmish goes full ECS # Skirmish started life with its game objects as good old-fashioned OO classes. In July that changed: first the game objects moved into the ECS (still hidden behind \u0026lsquo;increasingly thin classes\u0026rsquo;, as I put it at the time), and by the 22nd the characters themselves were being composed from components rather than inherited from classes. Its game phases also got broken out into distinct classes, with a \u0026lsquo;Conductor\u0026rsquo; organising the transitions and view changes between them.\nThat\u0026rsquo;s a big shift for one game, and it found two holes in Gremlin straight away.\nEntities can die now # Rather embarrassingly, Gremlin\u0026rsquo;s ECS could create entities but had no proper support for destroying them. A wargame where characters get taken off the board can\u0026rsquo;t really live with that. So July added entity destruction, plus debug assertions that catch any attempt to use an entity after it\u0026rsquo;s been destroyed.\nThe UI that wasn\u0026rsquo;t there # And a good bug: VisibilitySystem ignored the children of any parent that had no Renderable. The UI root is exactly such a parent, so whole UI trees could go unprocessed - invisible, and very confusing. It turned up the same day as a big simplification of Skirmish\u0026rsquo;s overlay construction code. The fix handles renderable-less parents properly, and caches the root parents it finds along the way.\nBits \u0026amp; bobs # Hover is now forced off when a hoverable is disabled, and the nine-patch hover isn\u0026rsquo;t drawn for things you can\u0026rsquo;t select. The space-creation helper for UI layouts takes a proportion. Tile maps report their full pixel dimensions and tile sizes. ","date":"31 July 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-07/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: July 2026 - Oh Snap","type":"gremlin"},{"content":"","date":"31 July 2026","externalUrl":null,"permalink":"/tags/ui/","section":"Tags","summary":"","title":"Ui","type":"tags"},{"content":" Snip, snip\u0026hellip; # June was a very busy work month, so a short one for Gremlin itself - a handful of commits - but a couple of them were big, and one of them touched every single app I have. There was also a brand-new game: a basic Breakout clone, because sometimes you just want to bounce a ball off some bricks.\nIMDraw: Push \u0026amp; Push3D # IMDraw had grown by accretion. Every new feature arrived with its own slightly different method names, and the old ones were kept \u0026lsquo;for compatibility\u0026rsquo;. Compatibility with what, exactly? It\u0026rsquo;s a library with one user!\nSo in June I culled them all in one go:\neverything is now Push*, anything flushed through the 3D pattern is Push*3D, and a redundant three-argument Draw overload is gone. The diff removed far more than it added, and every app in GremlinApps was fixed up the same day. That\u0026rsquo;s the \u0026rsquo;no backwards compatibility, just fix the callers\u0026rsquo; policy at work again. With one user, a coordinated rename is an afternoon, not a migration project.\nWatching the frame times # February\u0026rsquo;s frame-spike reporter grew up into a proper profiling system, with a debug overlay panel to show it. It lives in Gremlin rather than in each app, so any app gets it for free - MiniBlaster was the first to wear it.\nDropdowns, overlays \u0026amp; picking things # Two reusable bits of UI and interaction came out of the workbench tools this month:\nDropdown menus, with the shared \u0026lsquo;overlay\u0026rsquo; behaviour broken out into a reusable ECSOverlayState. Modals were rebuilt on top of it too, replacing April\u0026rsquo;s reusable modal states - and both CNCWorkbench and Skirmish moved over straight away. SelectionController, which picks objects in the 3D view by firing a ray into the scene, and supports selecting and transforming vector geometry. The selection work came with a new project, CADWorkbench. The modelling tools now have a focused space of their own to grow up in, away from everything else CNCWorkbench does.\nMeanwhile Skirmish got a better player-controller architecture, with CPU and human players behind the same interface - one of its two players is always the computer.\nBits \u0026amp; bobs # An explicit reset of the debug IMDraw, to dodge a problematic destruction order at shutdown. Removed the old WSL CMake preset - the plain Linux one does the job. What\u0026rsquo;s next? # Selecting things is only half of a CAD tool. CADWorkbench exists to work out the other half: drawing things.\n","date":"30 June 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-06/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: June 2026 - Pruning","type":"gremlin"},{"content":"","date":"30 June 2026","externalUrl":null,"permalink":"/tags/profiling/","section":"Tags","summary":"","title":"Profiling","type":"tags"},{"content":"","date":"30 June 2026","externalUrl":null,"permalink":"/tags/rendering/","section":"Tags","summary":"","title":"Rendering","type":"tags"},{"content":"","date":"31 May 2026","externalUrl":null,"permalink":"/tags/cameras/","section":"Tags","summary":"","title":"Cameras","type":"tags"},{"content":"","date":"31 May 2026","externalUrl":null,"permalink":"/tags/cnc/","section":"Tags","summary":"","title":"Cnc","type":"tags"},{"content":" Tidying up after the party\u0026hellip; # After April\u0026rsquo;s sprint, May was about consolidation. Fewer commits, but most of them were about turning \u0026rsquo;lots of shiny new capability\u0026rsquo; into \u0026lsquo;something I can actually work in\u0026rsquo;. Two projects pulled Gremlin along this month: CNCWorkbench (no surprise there) and Skirmish, which was getting its turn sequence and movement phase into shape.\nMy main distraction this month was my part in Vectric\u0026rsquo;s stand at Makers Central at the NEC in Birmingham. The new particled-up VictorVector was revealed to the Great British Public inside a Linux-driven, custom-made arcade cabinet.\nCNCWorkbench also got some pixel polish in order to drive the QuickDrawing app, which we ran on 4 old (but freshly Linuxed) laptops and the SainSmart Genmitsu CNC machines, so the kids (including some very big kids\u0026hellip;) could try their hand at racing Victor to draw a picture using custom GRBL support and an Xbox controller.\nOne document to rule them all # April left the CNCWorkbench data scattered all over the place - dexel stock in the CAD document, CSG somewhere else, toolpaths somewhere else again. In May the stock, CAD, CSG and toolpath records all moved into a single WorkbenchDoc, with serialisers and tests to match. On the app side, one DocRenderSystem now draws that document on every screen.\nContours also got simple integer IDs. Sounds trivial, but it means other things - like the toolpaths generated from a contour - can refer to them without Gremlin\u0026rsquo;s CAD code needing to know toolpaths exist. The dependency arrow keeps pointing the right way, and by the end of the month CNCWorkbench could regenerate every toolpath from the CAD elements that made it.\nThe arcball that argued with itself # The Arcball camera controller used to keep its own copy of the orientation, which then had to be kept in sync with the camera\u0026rsquo;s Transform. Two sources of truth for one thing? That\u0026rsquo;s a bug waiting to happen.\nSo I deleted the duplicate. The arcball no longer has any state of its own; it modifies the target entity\u0026rsquo;s Transform directly, applying per-frame deltas around an orbit centre. One source of truth, and CNCWorkbench\u0026rsquo;s main screens now share the same arcball, with gamepad bindings to \u0026rsquo;twiddle\u0026rsquo; the view.\nIt wasn\u0026rsquo;t plain sailing afterwards, mind. Later in the month CNCWorkbench developed what my commit messages describe, in technical terms, as \u0026lsquo;orbit craziness\u0026rsquo;. The root cause turned out to be toggling jogging on and off, which left jogging and the arcball competing for control of the camera.\nSkirmish wanted a different kind of camera altogether - looking down on the battlefield - so there\u0026rsquo;s now a dedicated overhead camera system with a default controller mapping too.\nRoom for more than one pass # CNCWorkbench now shows several viewports at once, which the standard \u0026lsquo;draw the scenes\u0026rsquo; path wasn\u0026rsquo;t built for. So DrawScenes has been broken out of Draw as a method you can override, and a state with unusual needs can take over the scene pass without rewriting the whole draw. Alongside it came:\n3D grid drawing on any plane - CNCWorkbench now shows the XZ plane and the machine extents too, new mesh primitives, so the cutting tools can be drawn as 3D meshes in the main views, a transparent pass in GeometryRenderSystem that writes depth, so see-through things still hide what\u0026rsquo;s behind them, 3D shape primitives that sort properly amongst billboards. Skirmish needed exactly this to fix its draw order. Same file, every OS # A small fix I\u0026rsquo;m oddly proud of. PropertyStore files are now binary identical whether they were written on Windows or Linux, and keys keep their order between load and save. Different line endings were making git think files had changed when they hadn\u0026rsquo;t, and shuffled keys made settings files impossible to compare by eye. There\u0026rsquo;s also a new SaveData type for simple, flat storage.\nBits \u0026amp; bobs # Fonts now come from the UI theme, with a sensible default when there isn\u0026rsquo;t one. A couple of small improvements to OpenGL debug output, for clarity. ","date":"31 May 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-05/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: May 2026 - Settling the Dust","type":"gremlin"},{"content":"","date":"31 May 2026","externalUrl":null,"permalink":"/tags/serialisation/","section":"Tags","summary":"","title":"Serialisation","type":"tags"},{"content":"","date":"30 April 2026","externalUrl":null,"permalink":"/tags/geometry/","section":"Tags","summary":"","title":"Geometry","type":"tags"},{"content":" Not a game in sight\u0026hellip; # OK, confession time. April was the busiest month of the year so far for Gremlin, and almost none of it was about games. My CNC router project, CNCWorkbench, wanted to load designs, trace bitmaps, generate toolpaths and simulate the cut - and Gremlin had to grow a lot of new muscle to keep up. This is an App that I want to use with a desktop CNC in a very interactive way.\nThis is the \u0026lsquo;solve the problem in front of me and let the structure emerge\u0026rsquo; idea in action. I didn\u0026rsquo;t plan to write a geometry library. One turned up because a project needed it.\nThis is also a bit of an overlap with my work life - but I have two specific requirements for this personal CADCAM project; I want a simple inconsequential coding space to work through crazier ideas that only may end up being usable at work, but I also have a sad, barely-used, Prusa printer by my desk. I would really like to generate something printable from first principles. Oh, and I have a vague electronics project in the back of my mind that may need circuit boards and enclosures. Obviously the solution is to build your own CADCAM library, right?\nGeometry grows up # Following the end-of-March split, Gremlin::Geometry became a proper home for:\ncylinder and capsule volumes, and line segments, path simplification algorithms, moved over from CNCWorkbench\u0026rsquo;s toolpath code, translating spans and contours, subtracting multiple intervals in one pass, to cut down on allocations and comparisons. The CAD document also gained a CadBitmap with boundary tracing, plus a small set of image-processing filters. So now a scanned or drawn image can be turned into contours to cut. CNCWorkbench was loading and tracing bitmaps the same day. This is really the first step on the way to a circuit trace engraving solution.\nSimulating sawdust # This was the deep end. To show what a cut will look like, you need a model of the material that the tools can remove from. Gremlin now has a tri-dexel stock model: three grids of rays through the block, one along each axis, where every ray stores a sorted list of solid intervals. Cutting is interval subtraction; adding material is interval union. Simple, right?\nWell, the maths is simple. The model renders by ray-marching in a shader, and most of the month went into getting the surface to look right. The key insight - written up in docs/DexelModel in the Gremlin repo - is that the interval boundaries are the surface. There\u0026rsquo;s nothing to search for. The hard part is deciding which of the three axes to believe at any given point, and getting a decent normal out of it:\nOne boundary in a cell? Use that axis\u0026rsquo;s height-field gradient. Easy. Two or three? Blend their normals, weighted by distance. The three-boundary case needs 12 texture reads, so it isn\u0026rsquo;t cheap. But it looks so much better that I decided it was worth it. And of course there were the usual GPU adventures: a rounding error that only showed up on one particular GPU (fixed by flooring byte decodes), moiré patterns, seam noise, and surfaces being clipped too early by a fixed step count (now a theoretical maximum passed in as a uniform). A long-overdue camera frustum arrived to help keep performance sensible, and the CPU-side surface queries even got unit tests. Who am I?\nCutter shapes (V-bits and friends) can now subtract themselves from the stock, and within a week CNCWorkbench was animating a V-bit carving its way through a sine-wave surface. By the 12th the old flat material quad was gone and the real stock model was simulating the current toolpath.\nCSG, OBJ \u0026amp; STL # Late in the month came an initial CSG implementation with creation and manipulation tools, mesh clean-up, serialisation and STL export. There\u0026rsquo;s basic OBJ saving now too (alongside the loading), and a native file-save dialog backed by tiny file dialogs.\nShaders that wire themselves up # A quieter change, but a really practical one. TriangleBatch and the GeometryRenderer now bind \u0026lsquo;standard\u0026rsquo; uniforms automatically, working out what each one is for from its name (see Shader::InitUniformMap()). You fill in a FrameUniforms struct - projection, view, camera and lights - once per pass, and an ObjectUniforms struct per object or batch. Anything left null just gets skipped.\nFar less boilerplate per shader, and a new shader \u0026lsquo;just works\u0026rsquo; if it follows the naming convention. While I was in there I also fixed the newer shaders to use Gremlin\u0026rsquo;s precision macros properly - I\u0026rsquo;d quietly broken ES 2.0 support, which is rather the whole point of having an ES 2.0 baseline. Oops.\nGoodbye, imgui # CNCWorkbench needed property panels, so Gremlin\u0026rsquo;s own UI grew an edit box, a translated TextInputEvent, and NumberEdit/TextEdit elements. Pocket toolpath settings were being edited through them within a couple of days.\nAnd that was the last thing I needed imgui for. So, on 27 April, imgui went. The commit is 9 lines added and 70,937 lines deleted.\nI like imgui a lot, honestly. But it was a big dependency doing a job my own UI can now do, and it meant a second UI paradigm living inside every app. Every dependency has to earn its keep, and this one had stopped earning.\nBits \u0026amp; bobs # Anti-aliased 3D line drawing, now used for all the line drawing in CNCWorkbench. A reusable ModalState with different styles, which hands back the user\u0026rsquo;s response. The perspective and orthographic cameras now behave consistently - similar view extents, panning and interaction. No more 16-bit index limit for OBJ files, now that CPUIndexArray exists. Float support in PropertyStore. Joystick enumeration fixes, and a simple button mapping for my arcade USB joystick interface - which meant VictorVector\u0026rsquo;s own joystick-inversion fix was now correcting a bug that no longer existed. Out it came. VictorVector got a particle effect while I was at it. Meanwhile Skirmish quietly started taking shape too, with its round and phase structure. What\u0026rsquo;s next? # The CNC side is now a pile of capable parts - stock, CAD, CSG, toolpaths - that don\u0026rsquo;t really live together yet. Pulling them into something coherent is the obvious next job.\n","date":"30 April 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-04/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: April 2026 - Sawdust in the Shaders","type":"gremlin"},{"content":"","date":"30 April 2026","externalUrl":null,"permalink":"/tags/shaders/","section":"Tags","summary":"","title":"Shaders","type":"tags"},{"content":" Two of everything\u0026hellip; # If February was moving house, March was the month I unpacked the boxes and noticed I owned two of everything. Two particle systems (2D and 3D). Several ways of saying something is visible. More collision systems than any sane person needs. Legacy renderers still hanging around next to their replacements. So March was mostly about getting down to one of each.\nConveniently, the games were asking for exactly that. Buccaneer and MiniBlaster both wanted fancier particle effects, and that\u0026rsquo;s hard to do nicely when there are two half-finished particle systems to choose from.\nVisible? Enabled? Tinted? # Visibility had grown messily: a separate Visibility component, per-component visible flags, and tint colours duplicated across the sprite, billboard and mesh components. It took three commits to untangle:\nRenderable now owns what applies to the whole entity - its tint colour, and whether it\u0026rsquo;s visible at all. The per-component flag is now called Enabled, because that\u0026rsquo;s what it actually meant all along: \u0026lsquo;process this component\u0026rsquo;. A disabled component isn\u0026rsquo;t drawn or updated. Render geometry (sprite, billboard, Mesh3D) lives separately from Renderable, so the duplicated fields are gone. VisibilitySystem reads Renderable directly, and the standalone visibility component has been binned. Even text labels lost their own colour field in favour of Renderable.tint. One way to say it. Lovely.\nOne particle system to rule them all # The 2D and 3D particle code had drifted apart, as copy-and-paste siblings always do. They\u0026rsquo;re now a single ParticleSystem with emitters for both 2D and 3D, and I finally sorted out who\u0026rsquo;s responsible for what between the system, the emitter and the particle.\nThe games got to play with the results almost immediately:\nBuccaneer got exhaust trails, explosions, and particle-based firing. A \u0026lsquo;dust field\u0026rsquo; in both Buccaneer and MiniBlaster, using new spawn volumes - including uniform distributions on a unit circle and sphere, which gave me a star-field effect almost for free. MiniBlaster got frame-animated explosions, thanks to new shared components that keep a particle\u0026rsquo;s lifespan in sync with its animation. The explosion finishes exactly when the particle dies, rather than vanishing mid-bang. Along the way particles picked up rotation and scaling (with initial values and variance), explosive bursts, and access to each emitter\u0026rsquo;s particle entities after creation, so you can dress them up with a billboard, sprite or mesh.\nBuccaneer\u0026rsquo;s exhaust trails, with the debug overlay along for the ride A MiniBlaster explosion, mid-bang The sprite component\u0026rsquo;s texture source also got split out into its own TextureSource component, which made the name SpriteSheet a bit of a fib. It\u0026rsquo;s now TextureAtlas.\nClearing out the old renderers # IMDraw - Gremlin\u0026rsquo;s immediate-mode drawing helper - and the triangle renderer under it now use shaders owned by February\u0026rsquo;s AssetManager, and RenderEffect became a managed asset in its own right. Billboards are now pushed through IMDraw\u0026rsquo;s list rather than drawn separately, which means they can finally be sorted against everything else IMDraw draws.\nWith that done, the legacy renderers could go. One commit deleted about 1,265 lines, which is my favourite kind of commit. And since the \u0026lsquo;unified\u0026rsquo; triangle renderer was now the only triangle renderer, UnifiedTriangleRenderer got a name that says what it actually does: TriangleBatch.\nA container choice worth writing down # One tiny commit deserves its own paragraph. Component pools moved from std::vector to std::deque. Why? Gremlin\u0026rsquo;s Transform parent/child links are raw pointers into the component pool, and a vector that reallocates as it grows silently invalidates every single one of them. A deque guarantees that growing at the ends never moves existing elements.\nThe cost is cache coherency. But Gremlin\u0026rsquo;s systems walk the components sparsely through entity masks anyway, rather than marching through the whole pool in order, so I judged that a fair trade. Ease of use over performance, as promised.\nNetworking, with ASIO out of sight # Gremlin\u0026rsquo;s networking started from Javidx9\u0026rsquo;s excellent ASIO-based approach, which is heavily templated on the message type. Templates everywhere means ASIO everywhere, and I didn\u0026rsquo;t want my games to have to know ASIO exists. So in March I de-templated the transport layer:\nNetServer and NetClient now use pimpl, behind a new INetConnection interface. Incoming traffic arrives as NetworkEvents delivered to AppState::HandleNetworkEvent - the same way OS events already arrive. ASIO is now a private dependency. No public Gremlin header includes it, so no app has to compile it. That\u0026rsquo;s exactly the abstraction pattern I described in Dependency Decisions, and Tanx, my networked tank game, moved over to the new architecture the same day.\nBits \u0026amp; bobs # Collision systems consolidated, with VolumeShape broken out for reuse beyond particles. Fixed a half-texel offset that was leaving visible seams on rotated sprites. New EntityBuilder helpers for common sprite set-ups. UI: checkbox styling, and a scrollable panel component with coverflow-like behaviour - CNCWorkbench had more buttons than screen. Layouts also rebuild when a state wakes up, in case the window was resized while it was dormant. At the very end of the month, maths, geometry and CAD concepts started separating into a new Gremlin::Geometry namespace (primitives, AABBs, spans, contours), leaving Gremlin::Cad as just the document model. What\u0026rsquo;s next? # That last bullet is only the start. The geometry split has barely begun, and the new toolpath work over in CNCWorkbench is hungry for more of it.\n","date":"31 March 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-03/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: March 2026 - One of Everything","type":"gremlin"},{"content":"","date":"31 March 2026","externalUrl":null,"permalink":"/tags/particles/","section":"Tags","summary":"","title":"Particles","type":"tags"},{"content":" Slow-mo Gamejam 1 - Feb 26 Sun 1 Feb – Sun 1 Mar 2026 Finished Home Rules Achievements Leaderboard Players Retrospective So, how did that go then\u0026hellip;? # OK, let\u0026rsquo;s cut straight to the chase - DickOfAxe won, with 60 points and a city builder that went from a grid and a snapping cursor to families, buildings and a working UI in four weeks. Congratulations! The prize, as advertised, is bragging rights and absolutely nothing else.\nBut the points were only ever a nudge. The real point of the month was steady progress, shared often, and on that score I think we all did rather well. Six of us started something, everyone posted along the way, and the threads filled up with screenshots, sound effects, assembly listings and some genuinely useful back and forth.\nFinal standings # # Player Points Badges Wk 4 🥇 🪓DickOfAxe Ancient Greek city builder 60 7 – 🥈 🖱️click Sigas Alpha 50 7 – 🥈 🥒Lintford Amiga Tetris (68000 assembly) 50 7 – 4 🐒VeraShackle Gremlin \u0026amp; Buccaneer 45 4 – 5 🫡Commander Coder The Retro Puzzle Box (virtual edition) 40 4 +10 6 🤓Alfie Minesweeper on a Cheap Yellow Display 15 3 – Cumulative points by day. The leaderboard table above has the same numbers. A confession from the referee: I never got round to scoring week 4, so these are the scores from the last scoreboard (end of week 3), plus a Bug Hunter that I\u0026rsquo;d already promised. Nobody was going to catch DickOfAxe anyway\u0026hellip; probably.\nWhat everyone made # 🪓 DickOfAxe - Ancient Greek city builder # \u0026ldquo;A new year, a new city builder.\u0026rdquo; The plan was a survival city builder set around an ancient Greek island, with an economy that doesn\u0026rsquo;t force the same production chain every game, and deliberately not a simulation - nobody wants to watch citizens spend a quarter of the year walking to the storeroom. Over the month it grew a generic grid, camera controls, a UI built in code with Unity\u0026rsquo;s UI Toolkit, building placement with validity checks, home-made 8-bit sound effects, and pops, families and regions underneath it all. Steady, visible progress every week, which is exactly what the points were meant to reward.\nDay 1: a generic grid, camera controls and a snapping cursor. Week 3: buildings, families and a drag box for work areas. 🖱️ click - Sigas Alpha # The most ambitious foundations of the month: a multiplayer game server written from scratch, with an architecture diagram on day one. By the end there was a Java NIO broker passing chunked-encoded message streams between players, a Flask hub for creating and joining games, a Python client library, and a hand-written JSON lexer and parser (because, why not?). The card game it\u0026rsquo;s all for is still to come, but the plumbing is in.\nclick\u0026rsquo;s architecture on day one: clients, a hub for finding games, and brokers passing messages to the game servers. There was also a convincing case for a \u0026lsquo;First GitHub commit\u0026rsquo; achievement, which has been passed to the committee.\n🥒 Lintford - Amiga Tetris # Tetris, on an Amiga 500 spec, in 68000 assembly. Lintford started with a reusable AmigaBase of common routines, built an interactive display-window visualiser along the way (admittedly overkill, but very slick), then worked through game states as jump tables, tetromino masks, blitter-based collisions and - after days of wrestling the blitter in descending mode - line clearing. The posts explaining the code were some of the best reading of the month.\nThe display-window visualiser: set the Amiga\u0026rsquo;s display registers and see where the beam fetches data. Week 3: blocks spawning, moving and stacking in the well.\n🐒 VeraShackle - Gremlin \u0026amp; Buccaneer # Me. I started with a long-overdue untangling of Gremlin into proper layers, which (with some help from click) turned into a much bigger refactor than planned. Then flight physics came back to Buccaneer, a frame-spike profiler found some wasteful GPU uploads, an asset management system appeared out of a simple wish to share a sprite sheet, and a new UI toolkit finally got used in anger. The month ended with ECS-driven particles and spaceships doing nearly the right thing. Very little of it was what I planned on day one - which, if you\u0026rsquo;ve read my other posts, will surprise no-one.\nFlight physics back in Buccaneer - and, as a bonus, it\u0026rsquo;s portable. The new UI toolkit, finally used in anger. 🫡 Commander Coder - The Retro Puzzle Box # A virtual puzzle box with a 1980s home computer theme on each of its six faces - rubber keys, cassettes, BASIC, pixel graphics, SID chip music and a 6809 CPU - built in Godot, with a view to a real ESP32-powered box one day. The puzzles for every face got designed, there were experiments with trackers for the music, and a sliding peg-in-a-maze mechanism that nearly behaved. Possibly the best idea of the month, and one I\u0026rsquo;d really like to see finished.\nThe plan for the six faces, one 1980s home computer puzzle on each. 🤓 Alfie - Minesweeper on a Cheap Yellow Display # Alfie joined late, with a Cheap Yellow Display (an ESP32 with a touchscreen) bought without a purpose. In ten days that became a C project with PlatformIO and LVGL, touch input sorted out (rotation included), and a 9x9 grid that can tell a press from a long press. It ended in what Alfie called a \u0026lsquo;dead end\u0026rsquo; with LVGL\u0026rsquo;s button styling, but really that\u0026rsquo;s a design blind alley found and crossed off - which is progress too.\nThe Minesweeper grid running on the Cheap Yellow Display. The weekly twists # Each Monday brought an optional 10-point twist:\nWeek 1 - Make Some Noise: an audio asset of your own creation. DickOfAxe stitched together 8-bit samples into building and demolish sounds. Commander Coder and I collected too. Week 2 - Juice Budget: three small feedback improvements. No takers - probably a bit early in everyone\u0026rsquo;s projects. Week 3 - One Value, Many Effects: one variable driving several systems. Week 4 - Bling it up: a before and after of an upgraded art asset. Week 1\u0026rsquo;s twist was claimed three times and the rest not at all, which tells me something about timing - twists work better when they fit where people\u0026rsquo;s projects naturally are.\nWhat I\u0026rsquo;d change # Score every week, including the last one. The final week deserves a final scoreboard, not a referee on a school night. Keep the scores in public. The scoring lived in my spreadsheet, and only the weekly totals made it to Discord. Claims should be visible as they happen, not just once a week. Notifications for threads were a pain. Some of us only kept up by posting in every thread! Earlier achievements for \u0026lsquo;plumbing\u0026rsquo; projects. A few of us spent the whole month on foundations, and the list favoured visible game progress. Fair enough for a game jam, but worth balancing. Thanks everyone - it was a lot of fun.\n","date":"8 March 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/retrospective/","section":"Jams","summary":"","title":"Retrospective","type":"jams"},{"content":"","date":"28 February 2026","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"","date":"28 February 2026","externalUrl":null,"permalink":"/tags/assets/","section":"Tags","summary":"","title":"Assets","type":"tags"},{"content":" Boxes everywhere\u0026hellip; # OK, February was the month Gremlin moved house. Lots of boxes got carried from one room to another, a few things got thrown in the skip, and I finally fixed some plumbing that had been leaking for ages. It was also the busiest month of the year so far - which, after January, isn\u0026rsquo;t saying much - and almost all of it was foundational stuff.\nA new floor plan # On 1 February I finished the restructure I started in January, in one long afternoon of renames. Framework/ became Core/, Gfx/ became Platform/, the systems and components moved under ECS/, and the app loop, AppObjects, states and transitions all got their own App/ folder. A few days later the dependency-free data components - plain structs that don\u0026rsquo;t need the ECS to make sense - moved down into Core too.\nWhy bother? Because the folders now tell you which way the dependencies point:\nCore depends on nothing. Platform wraps the outside world - windows, the GPU, files. ECS builds on both. App ties it all together. When I come back to Gremlin after a few weeks away - which, let\u0026rsquo;s be honest, is the normal state of affairs - I want the layout itself to tell me where something belongs. I shouldn\u0026rsquo;t need to remember. My experience with the r36 Max last month really emphasised the benefits of keeping clean dependency architecture and now I feel I\u0026rsquo;ve taken the first steps towards being able to build Gremlin in different ways for different tasks.\nThere is a thin line between clever\u0026hellip; and stupid! # This is my favourite change of the month. Gremlin\u0026rsquo;s IndexBuffer used to keep a CPU-side copy of its data and upload it implicitly inside DrawIndexedTriangles whenever it had changed. Convenient? Yes. Also a lovely way to get frame stalls - an upload happening in the middle of drawing, at a moment you didn\u0026rsquo;t choose.\nSo the two ideas are now completely separate:\nCPUIndexArray and CPUVertexArray are plain CPU-side containers you can build and fiddle with freely. GPU buffers hold no CPU-side data at all. You Set them explicitly from a CPU array before drawing, and a draw call never uploads anything behind your back. There are convenience factories to create a GPU buffer straight from a CPU array, so the explicit version isn\u0026rsquo;t any more typing. Typed vertex structs (SpriteVertex and friends) replace raw std::vector\u0026lt;float\u0026gt; and byte-pushing. One renderer lost about 40 lines of hand-built attribute descriptions to a single GetVertexAttributes() call. Nice. What gave the game away was a new debug system profiler, which landed the day before the fix. Debug builds now shout about any AppObject whose frame time spikes to 5x normal or more, and once Buccaneer started throwing particles and billboards around, it had plenty to shout about. Without it I\u0026rsquo;d have just had a vague feeling that things were a bit juddery.\nWho owns this texture? # Until now, textures were passed around as shared_ptr\u0026lt;Texture\u0026gt;, which really means \u0026rsquo;nobody is quite sure who owns this, so everybody does\u0026rsquo;. February brought a proper AssetManager, with handles, pools, policies (persistent, or scoped to a state) and AssetScopes that release a state\u0026rsquo;s assets when it goes away.\nNow that ownership is clearly the manager\u0026rsquo;s job, everything else can go back to holding a plain Texture*. I know some of you will be clutching your smart pointers in horror, but it\u0026rsquo;s actually more honest: the raw pointer says \u0026ldquo;I\u0026rsquo;m only borrowing this\u0026rdquo;, and the manager is the one place that decides how long it lives.\nA few related tidy-ups came along for the ride - texture source rectangles are now uint32_t (a negative pixel coordinate in a texture is meaningless), there\u0026rsquo;s optional mipmap generation on load, and textures can be created from data as well as from files. Every app\u0026rsquo;s texture loading moved over to it the same day.\nI\u0026rsquo;m planning a separate post on asset management, so I\u0026rsquo;ll save the details for that.\nUI, take two # The old UISystem and ButtonSystem were monoliths - one system each doing layout, input, state and drawing for every widget. That\u0026rsquo;s exactly the kind of thing an ECS is supposed to save you from, so they\u0026rsquo;re gone, replaced by lots of small components and systems that each do one job:\nNavigation - a new navigation layer connects the ActionController (gamepad, keyboard) to the InteractionSystem, so menus work without a mouse. Rendering - all UI drawing now happens in one UIRenderSystem. Theming - UITheme supports nine-patch textures, plus overlay patches for richer hover states. Selected items can gently pulse their background colour, thanks to a new Colour lerp. With the new navigation layer, a gamepad is all you need to get around the menus Sprite also moved down into the Platform layer as the way sprite data gets passed around - sprite sheets, animation and UI all speak it now.\nRetiring the old systems deleted about 1,400 lines. It also meant every app had to move to the new patterns in the same week: CNCWorkbench, Skirmish, MiniBlaster and Cannonade all picked up the themed nine-patch buttons, and Skirmish got the fancy hover overlays. That\u0026rsquo;s the price of having no backwards-compatibility policy\u0026hellip; and also the joy of it.\nBits \u0026amp; bobs # A few smaller things worth a mention:\nScreenWidth/ScreenHeight can be overridden from Settings.txt, and VictorVector got a basic layout that adapts to different screen sizes. Fixed some incorrect indexing for non-square textures. The old particle class, already replaced by ParticleSystem + BillboardRenderer, finally got deleted. Buccaneer got physics and some fixed input controllers. A first Cad data document with SVG load and save, for some CNC experiments. ","date":"28 February 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-02/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: February 2026 - Moving House","type":"gremlin"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/cross-compile/","section":"Tags","summary":"","title":"Cross-Compile","type":"tags"},{"content":" So, what have I actually been doing? # OK, the design principles posts are all very well, but talk is cheap. This is the first of what I hope will be a monthly dev log - what actually changed in Gremlin each month, what made me change it, and (inevitably) which decisions I\u0026rsquo;m already regretting.\nJanuary was a quiet month by commit count - the festive season does that to a hobby - but there were two clear themes: getting Gremlin onto whatever devices catch my attention (in this case a Christmas present), and tidying the codebase so I can actually find things in it!\nThe R36 Max # Previously in Dependency Decisions I said an important part of going through the pain of developing my own toolkit is to allow me to make things for whatever devices \u0026lsquo;happen to interest me\u0026rsquo;. Well, one did. The R36 Max is one of those inexpensive ARM Linux handhelds - a little games console running a stripped-down Linux - but of course I don\u0026rsquo;t feel like I really own it until I get my own Gremlin game running on it!\nChristmas Day: the R36 Max, fresh out of the box and still blissfully Gremlin-free The catch with these devices is that you don\u0026rsquo;t build on them, you cross-compile for them, against a sysroot image copied off the device. So Gremlin\u0026rsquo;s CMake needed teaching a couple of new tricks:\nUse the device\u0026rsquo;s SDL2, not mine. A new CMake flag forces Gremlin to pick up SDL2 from the cross-compiled sysroot rather than building its own. The device\u0026rsquo;s SDL2 already knows how to talk to its own screen and buttons. Someone else has done the hard platform work - this is exactly the SDL2 ubiquity I was so pleased about. Nice. Route OpenAL through SDL2. The same flag forces OpenAL Soft to use SDL2 as its audio backend, so only one thing on the device needs to understand the audio hardware. Over in GremlinApps, linking the Wayland client library became an explicit option too. The R36 Max runs Linux, but without Wayland, and it turns out I had quietly assumed that \u0026ldquo;Linux\u0026rdquo; meant \u0026ldquo;Wayland\u0026rdquo;. It doesn\u0026rsquo;t.\nAnd then git got involved. My .gitignore excluded binaries globally, which is normally exactly what you want\u0026hellip; except a cross-compile environment needs a few specific libraries from the sysroot committed. Cue one carefully targeted exception file.\nSo, did it work? Oh yes! Lots of my games ran on it - MiniBlaster, Cannonade, VictorVector and Buccaneer all made it onto that little screen. I\u0026rsquo;ll admit VictorVector\u0026rsquo;s title screen wasn\u0026rsquo;t quite expecting such a square display, and \u0026lsquo;Help Victor VCarve his way to victory\u0026rsquo; loses the end of a few words, but that\u0026rsquo;s a problem for another day. It\u0026rsquo;s mine now :)\nWho\u0026rsquo;s that entity? # ECS architectures are wonderful right up until you want \u0026rsquo;the score label\u0026rsquo; or \u0026rsquo;the OK button\u0026rsquo; and remember that an entity is just a number. Referring to them later is awkward unless you hold on to increasing numbers of numbers!\nThe culprit this time was Skirmish, my two-player sci-fi wargame, which needs a lot of UI - and UI is exactly where you keep wanting to find \u0026rsquo;that button\u0026rsquo; again. So January brought an EntityQueryService. Entities can carry an optional EntityName component, and a cached service resolves names back to entities. You can get at it from both Scene and ECSManager, and UIBuilder now names the entities it creates, so UI code can find its own buttons and labels again without clinging on to raw entity IDs.\nThe UI layout code got a bit of love at the same time - some redundant code removed, a clearer statement of what a layout actually needs, Visible components added by default, and the grid layout support I\u0026rsquo;d somehow managed to leave out entirely.\nThe great rename begins\u0026hellip; # The last week of January started a tidy-up I\u0026rsquo;ve been putting off for far too long. Gremlin had two ECS namespaces - an older ECS and a newer GremlinECS - the fossilised remains of a migration I never quite finished. On the 27th, GremlinECS finally went, and everything lives under one ECS again.\nThen I pulled out the ECS-agnostic bits - the interfaces that let any object hook into the app\u0026rsquo;s update and draw loop - gave them a new \u0026lsquo;AppObject\u0026rsquo; naming convention, and moved them out of the ECS namespace altogether. Why? Because the ECS is one user of the app loop, not the owner of it, and the code should say so.\nThis is the kind of change that touches 70-odd files and alters no behaviour whatsoever, which is precisely why it\u0026rsquo;s so easy to keep putting off. And it isn\u0026rsquo;t finished yet, either - there\u0026rsquo;s plenty more still to move.\nWhile I was in there, I also took fullscreen out of the preprocessor in the apps (it was breaking web embedding). It\u0026rsquo;s now decided at runtime from Settings.txt, which is where it should have been all along.\nWhat\u0026rsquo;s next? # Finishing that restructure, which is currently half done and half confusing :)\n","date":"31 January 2026","externalUrl":null,"permalink":"/gremlin/devlog-2026-01/","section":"Gremlin Engine","summary":"","title":"Gremlin Dev Log: January 2026 - Handhelds \u0026 Housekeeping","type":"gremlin"},{"content":" Decisions, decisions\u0026hellip; # OK, let\u0026rsquo;s cut straight to the chase - the first problem with starting something you expect to work with for years is the old \u0026lsquo;paralysis by analysis\u0026rsquo;! There are any number of choices you can make that you expect to live with for a long time and so it is easy to stall right at the gate. But hobby coding life is a bit different. I can actually change any and every aspect of this project in due course - the important thing is it will be my decision if, and when, I choose to do so. This is actually quite a radical thought for a professional developer used to all the constraints of the commercial work environment. Just relax into it - agree that whatever you decide now will change in the future but you will always own the choice, the timing and the outcome.\nNext important personal agreement with myself, unlike in my professional life, is that throughout I am going to deliberately try to only address the immediate problem in front of me for the project I am currently interested in - maintaining momentum and exploring problems using code is more important than up-front planning or ideology. I am going to embrace the idea that good code structure for a game toolkit will emerge as I use it to solve problems in a broad range of projects! I expect to refactor a lot and iterate towards solutions.\nOK - so what can\u0026rsquo;t I defer or avoid right now? Well, I guess I need to pick a language.\nLanguage \u0026amp; Build System # Despite a lot of time in C# land during my XNA days, and the serious pull of Rust in recent years I am developing my library in C++17. Whoa! I know some of you have already dropped your biscuits into your tea, and your tea onto your keyboards, so let me layout the case for the defence:\nThis is a long-term project with stability at its heart - I don\u0026rsquo;t think C++ will let me down in this front. C++ really has already stood the test of time and the standard is truly open - Flash game anyone? remember Silverlight? No, thought not\u0026hellip; sadly for me, I do. Standard updates are never forced - C++17 should keep me going for decades. Changes to the standard are almost never breaking (std::auto_ptr was obviously for perverts). libraries, APIs, support, examples and documentation are all going to be mature and widely available. I\u0026rsquo;ve been using C \u0026amp; C++ for 30 years and have already got over most of its peculiarities and foibles. I just really need to resist the overhead learning another language as well :) ! I do, though, really want to understand the details of how things work. In not hiding much of the system beneath I can convince myself this is actually a benefit of C++? No, I mean that, honestly. I will try to mitigate the downsides with some well-established idioms, coding patterns and practices - hopefully more on this stuff once we get into the meat and potatoes later in this series.\nMy other choice at this point is CMake to wrangle those cross-platform builds. Yes, I know it is drawback of the C++ choice that I even need to consider a separate build system and, yes, it is pretty horrible. But it is the least worst option for now. It does mean I am not dependent on a particular IDE (Visual Studio, Visual Code, CLion \u0026amp; Xcode will all provide support) and I can, and do, simply use command line, terminal or ssh for native builds.\nI\u0026rsquo;m compiling using msvc on windows, apple-clang on MacOS, clang and gcc on Linux. I\u0026rsquo;ll get into the details about my CMake structure in a future post.\nGraphics API # OK, next most controversial decision is that I target OpenGL ES 2.0 as my graphics baseline. Not Vulkan, not even modern OpenGL. ES 2.0, from 2007. Why?\nThe practical answer is portability. ES 2.0 runs everywhere I care about: desktop Linux and Windows, macOS (for now), Raspberry Pi, web browsers via Emscripten, and even obscure ARM handhelds like the R36 Max. A shader I write once actually works on all of these without (much) modification. That\u0026rsquo;s genuinely valuable when you\u0026rsquo;re a single developer who doesn\u0026rsquo;t want to maintain multiple rendering backends.\nThe honest answer is also that I\u0026rsquo;m not building the next Unreal Engine. I don\u0026rsquo;t need compute shaders or tessellation or bindless textures. For the kinds of games I make - 2D sprite-based things, simple 3D scenes - ES 2.0 is entirely sufficient. Choosing a \u0026ldquo;better\u0026rdquo; API would add complexity without enabling anything I actually need.\nThe cost is real though. There\u0026rsquo;s no geometry shaders, no instancing (well, there\u0026rsquo;s an extension, but patchy support), no modern debugging tools. When I do want a more advanced effect, I have to get creative or accept the limitation. And there\u0026rsquo;s always that nagging feeling that I\u0026rsquo;m learning an obsolete API.\nI have, as with some other dependencies, built a simple abstraction layer between most of my rendering code and the OpenGL ES2 GraphicsDevice implementation so I am leaving the door open for a future implementation on top of a different graphics API\u0026hellip;\nDependencies # My starting point here was no complete dependencies so, as with the OpenGL ES 2 graphics device, almost all 3rd party libraries (see below) are hidden from the core Gremlin classes behind a thin abstraction layer - with one exception.\nDirect Dependency - Maths \u0026amp; Linear Algebra # I have previously written my own fairly extensive geometry and linear algebra library, but I honestly never perceived any tangible benefit from this. Even in a hobby project I feel compelled to write extensive unit tests for library such as this. If cross-platform, cross-compiler and multi-architecture is going to burn me anywhere it will be the hard to debug subtleties of precision and low-level maths algorithm performance and behaviour. This is just a headache I really wanted offloaded to a much more widely tested framework.\nGLM # I chose the header-only OpenGL maths library GLM because it is mature, robust and has common conventions with glsl shader maths. I did not write any abstraction layer here. Although I have stated I am not worrying unduly about performance issues, vector/matrix data and functions are utilised extensively on the hot path of every application and so the penalties of any indirection just can\u0026rsquo;t be ignored.\nAbstracted Dependencies # I will discuss this pattern in more detail in later posts, but I have used the Private Implementation (pimpl) idiom extensively to create an abstraction layer between specific 3rd implementations of key functionality.\nOS Window, GFX Context Creation \u0026amp; User Input - SDL2 # You will note a theme developing - I have deliberately stayed off the bleeding edge whenever possible. SDL3 was similarly avoided for now. I have used SDL2 to back my OS window and OpenGL context creation. This has proven a fantastic decision so far because SDL2 is just so mature and ubiquitous that in several important cases I have been able to utilise existing system setup or other trail-blazers\u0026rsquo; work to get my stuff up and running on the back of pre-existing SDL2 support. Nice.\nAudio - OpenAL # Not sure about the OpenAL API - it is meant to be OpenGL-like but I was glad to get it wrapped up and then not have to touch it again! It is a more powerful API but I could probably have leveraged the SDL2 audio mixer with the same effort for my simple use case. Actually I have been pleased with the choice though, as there has always been extensive existing support for OpenAL everywhere I have needed it.\nPNG images \u0026amp; TTF fonts - Sean Barrett # I\u0026rsquo;ve use two of Sean\u0026rsquo;s header only classes (via a pimpl interface) from his STB library of really useful classes for my bitmap image and ttf support. Probably revisit this library for ogg vorbis audio file support at some point too. Thanks Sean!\nAI? # Yep, I am not taking any ideological position on this - my sycophant of choice currently is Claude Code. I still tend to do a lot of typing myself (because I like it) so Claude helps with true boilerplate shizzle or as a planning and research tool primarily. In due course, I may use AI for art asset creation but, as with the coding, I am doing this stuff as leisure activity, so I want to make art, sound and music - that is all an intrinsic part of the fun! So, as with Claude, I expect I will look more for inspiration than implementation.\n","date":"25 January 2026","externalUrl":null,"permalink":"/gremlin/dependency-decisions/","section":"Gremlin Engine","summary":"","title":"Dependency Decisions","type":"gremlin"},{"content":"","date":"25 January 2026","externalUrl":null,"permalink":"/tags/game-engine/","section":"Tags","summary":"","title":"Game Engine","type":"tags"},{"content":"","date":"25 January 2026","externalUrl":null,"permalink":"/series/gremlin-design-principles/","section":"Series","summary":"","title":"Gremlin Design Principles","type":"series"},{"content":" OK, let\u0026rsquo;s get the rant out of way\u0026hellip; # I like coding for fun, but like most grown-ups I am always struggling to make space in my busy calendar to enjoy quality time with my keyboard. Mostly weeks, but sometimes months, go by between my programming sessions - no point pretending, this is just how it is. Like many people, I have thought the solution to this problem is a really good framework that provides all that boilerplate functionality I need to get interactive graphics on the screen and a pre-built extensive (end extensible) library of key features and functions as a \u0026lsquo;force multiplier\u0026rsquo; of my limited coding time, right?\nOver the years I have been tempted many times into the latest-greatest-fancy-pants coding framework of the moment XNA, Mono, Unity, Unreal, Godot\u0026hellip; All of them have started well with a great weekend and yet another port of my project up and running in, what feels like, no time\u0026hellip; that old framework is dead to me, this is the one!\nBut in the ensuing months, and sometimes years, they have also all ended in the same way. Instead of progressing my ideas, each precious coding opportunity gets increasingly consumed by frustrating \u0026lsquo;busywork\u0026rsquo; generated by the framework provider - enforced updates, breaking changes, deprecated concepts, sudden changes of direction, failing new functionality (which I never even wanted). I think I can just stay on the old version, but v1.2.3.4.32.2.1 happens to be the one with that bug right in the middle of feature I am working on - it\u0026rsquo;s not a high priority for the framework devs (all lovely people, I know). They\u0026rsquo;ve already moved onto chasing the gesture support, Mac integration, VR, Vulkan etc. etc. to keep their framework \u0026lsquo;current\u0026rsquo;. Eventually the bug may be fixed but, more likely, the code is simply deprecated and replaced with a shinier new pattern that avoids the problem! My only option is the dreaded upgrade (with the ensuing tangle of incompatible dependencies) and porting of my hard-won but now also suddenly deprecated code. Although, if I am going to have to do that\u0026hellip; everyone is telling me I really should move this new framework that solves all the problems of the old one\u0026hellip;\nEither way I\u0026rsquo;m going to be spending my weekend porting again! Arrghh!\nA couple of years ago my precious leisure coding time was actually more frustrating, more stressful and frankly more joyless than my work! I had dozens of projects unfinished (even to my rather low standards) simply because my hobby coding rate could not keep up with demands of my framework provider! To get an interesting idea up and running may take me several years of unpredictable weekends and maybe a whole week in the summer or over Christmas. I can\u0026rsquo;t finish things faster than they can deprecate them!\nI was doing entirely the wrong calculation of value. It doesn\u0026rsquo;t matter how quickly a framework gets my project started or it\u0026rsquo;s power and future potential if I never have the time required to leverage any of that before it is deprecated and replaced!\nTime to get off the hamster wheel\u0026hellip;\nBack to Basics! # My mates are using many of the frameworks described above - Monogame, Unity, Godot - and there are endless discussions about the competing features of each but what are my actual priories in a hobby development toolkit?\nAbove all else I need stability - I expect long project cycles so no behind-the-scenes breaking changes. I can\u0026rsquo;t really afford to write things twice - I want re-usable code to be flexible. I need to be able to accumulate useful code to use over many years - re-usable code needs to be robust \u0026amp; easily tested. I want to be able to swap projects on a whim and work on new features immediately - this is a hobby right? I need to spin up a new project easily and quickly - minimal friction in getting my idea onto the screen. I want the cross-platform capability to deploy to devices that happen interest me - currently x64 Windows, x64 Linux \u0026amp; ARM Linux (RPi), but who knows about next week? What don\u0026rsquo;t I need? I think this is almost as important to be clear about from the outset:\nI don\u0026rsquo;t need bleeding edge graphics performance - I\u0026rsquo;m very much limited by my art skills in any case! Ease-of-use will almost always trump performance requirements. It is my personal library - I\u0026rsquo;m not designing it for other people to use. Summary # So there you have it! Gremlin is a very personal project and these issues were my motivation. As I have stated above, it is not intended to be shared directly but maybe my experiences and motivations chime with your own? Maybe writing up my experiences and decision making processes will be helpful if you consider embarking on a similar project?\nIn my next post I will discuss my initial technical choices to try and satisfy these requirements.\n","date":"25 January 2026","externalUrl":null,"permalink":"/gremlin/what-is-gremlin/","section":"Gremlin Engine","summary":"","title":"What Is Gremlin?","type":"gremlin"},{"content":"Small, achievable steps: most should take an hour or two at most.\n","date":"25 January 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/achievements/","section":"Jams","summary":"","title":"Achievements","type":"jams"},{"content":"","date":"25 January 2026","externalUrl":null,"permalink":"/tags/challenge/","section":"Tags","summary":"","title":"Challenge","type":"tags"},{"content":"","date":"25 January 2026","externalUrl":null,"permalink":"/tags/coding/","section":"Tags","summary":"","title":"Coding","type":"tags"},{"content":"From the referee\u0026rsquo;s scoring spreadsheet, which fed the scoreboards posted in Discord each Monday. The last one went up at the end of week 3, and week 4 was never scored, so these are the final standings.\n","date":"25 January 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/leaderboard/","section":"Jams","summary":"","title":"Leaderboard","type":"jams"},{"content":"","date":"25 January 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/","section":"Jams","summary":"","title":"Players","type":"jams"},{"content":" Slow-mo Gamejam 1 - Feb 26 Sun 1 Feb – Sun 1 Mar 2026 Finished Home Rules Achievements Leaderboard Players Retrospective I\u0026rsquo;ve created the following rules, points and achievements for the sole purpose of giving us all little measurements of progress to share over the course of a month.\nDuration # Sunday 1 Feb 2026 - Sunday 1 Mar 2026 (4 weeks) You can work at your own pace — no daily requirements What Counts as a Project? # Anything game-adjacent counts:\nGames, toys, experiments, interactive tools Any engine, framework, language, or even paper prototypes Solo or collaborative (just be clear what you worked on) Achievements \u0026amp; Points # There are 30 achievements available during the month Each achievement can be earned once Achievements are intentionally small and achievable Most should take ~1–2 hours or less To earn an achievement:\nDo the thing Share some evidence in Discord\n(screenshot, clip, build, explanation, or short write-up) Claim the achievement I will try and keep tabs and post a weekly progress summary.\nPoint Values # 5 pts – Very small progress (≈30–60 min) 10 pts – Solid focused work 15 pts – Meaningful milestone or iteration 20 pts – Harder, integrative, or “shareable” progress Points are for fun, bragging rights, and momentum only.\nWeekly Mini-Twists # Each week, the I will announce one optional mini-twist.\nMini-twists are:\nLight constraints, incentives, or creative lenses Optional, never mandatory Designed to encourage focus, experimentation, or sharing Participating in a weekly twist will usually:\nAward bonus points, or Unlock a special one-off achievement Details will be revealed at the start of each week to keep things fresh.\nSharing Guidelines # Sharing is the heart of the challenge Good shares include:\nScreenshots or short clips “Before / After” comparisons A few sentences about what changed or what you learned Code snippets or system explanations WIP is encouraged. Rough is good. Broken is fine.\nFinishing \u0026amp; Reflection # You do not need a “perfect” or “complete” game. A playable build, a toy, or a clear ending is a win.\nAt the end of the 30 days:\nShare what you made Reflect on what worked and what didn’t Celebrate finishing something ","date":"25 January 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/rules/","section":"Jams","summary":"","title":"Rules","type":"jams"},{"content":"Game Jam\u0026rsquo;s are great and all that, but I find that they can be quite incompatible with busy \u0026lsquo;grown-up\u0026rsquo; life :) That said, I really like the motivation of a focused period of time and a common challenge with friends. I\u0026rsquo;m wondering if we can create an alternative \u0026rsquo;event\u0026rsquo; with less intensity, less pressure and more flexibility? I want to retain some gentle competition but this is not about grinding, perfection, or \u0026lsquo;winning\u0026rsquo; anything. Sharing progress in the challenge might also help in raising each others game (quite literally).\nThe goal is simple: encourage steady progress, share it often, and help each other finish more things!\nRead the rules and browse the achievements.\n","date":"25 January 2026","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/","section":"Jams","summary":"","title":"Slow-mo Gamejam 1 - Feb 26","type":"jams"},{"content":"A central cannon - rotate, elevate, shoot. Defend your cannon from the marauding\u0026hellip; er cube things.\nEsc. To return to the title screen and restart.\n","date":"19 January 2026","externalUrl":null,"permalink":"/games/cannonade/","section":"Games","summary":"","title":"Cannonade","type":"games"},{"content":"","date":"19 January 2026","externalUrl":null,"permalink":"/tags/action/","section":"Tags","summary":"","title":"Action","type":"tags"},{"content":"Who doesn\u0026rsquo;t like spaceships? Currently just a rendering test using the fantastic models created by Griff for the oolite project. Shows diffuse, specular, normal mapping and glow effects.\nHow to Play # Mouse - Aim cannons ","date":"19 January 2026","externalUrl":null,"permalink":"/games/buccaneer/","section":"Games","summary":"","title":"Buccaneer","type":"games"},{"content":"","date":"19 January 2026","externalUrl":null,"permalink":"/tags/elite/","section":"Tags","summary":"","title":"Elite","type":"tags"},{"content":" The Magic Monkey Nuts of Internet Goodness # This is my place on the global information super highway. It is where I share the things I am working on with my mates, but you\u0026rsquo;re welcome to stay and have a look around.\nGremlin Lib # This is my personal experimental C++ libary of graphics and games tools. I use it as the basis of whatever project takes my fancy.\nGames # Gremlin supports web assembly builds and despite their completely unfinished state I sometimes post and update projects here so my mates can take them for a spin.\nBlog # I like thinking about code patterns and architecture, in particular, and will sometimes post my musings here.\n","externalUrl":null,"permalink":"/about/","section":"Magic Monkey Nuts","summary":"","title":"About","type":"page"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/alfie/","section":"Jams","summary":"","title":"Alfie","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/alfie/","section":"Jams","summary":"","title":"Alfie","type":"jams"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/charles/","section":"Jams","summary":"","title":"Charles","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/click/","section":"Jams","summary":"","title":"click","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/click/","section":"Jams","summary":"","title":"click","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/commander-coder/","section":"Jams","summary":"","title":"Commander Coder","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/commander-coder/","section":"Jams","summary":"","title":"Commander Coder","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/dickofaxe/","section":"Jams","summary":"","title":"DickOfAxe","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/dickofaxe/","section":"Jams","summary":"","title":"DickOfAxe","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/lewis/","section":"Jams","summary":"","title":"Lewis","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/lintford/","section":"Jams","summary":"","title":"Lintford","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/lintford/","section":"Jams","summary":"","title":"Lintford","type":"jams"},{"content":" Privacy Policy # This site uses privacy-friendly analytics and commenting systems that respect your privacy.\nComments # Comments are powered by Giscus, which uses GitHub Discussions. When you comment, you\u0026rsquo;re interacting with GitHub\u0026rsquo;s platform. Please refer to GitHub\u0026rsquo;s Privacy Policy for information about how your data is handled.\nCookies # This site uses minimal cookies necessary for basic functionality and appearance preferences (dark/light mode).\nThird-Party Content # Embedded content from third-party services (YouTube videos, itch.io widgets) may set their own cookies and tracking. These are loaded only when you interact with them.\nContact # If you have questions about this privacy policy, please contact us through the links provided in the footer.\nLast updated: January 5, 2026\n","externalUrl":null,"permalink":"/privacy/","section":"Magic Monkey Nuts","summary":"","title":"Privacy Policy","type":"page"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/simon/","section":"Jams","summary":"","title":"Simon","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-1-feb-26/players/verashackle/","section":"Jams","summary":"","title":"VeraShackle","type":"jams"},{"content":"","externalUrl":null,"permalink":"/jams/slow-mo-2-nov-26/players/verashackle/","section":"Jams","summary":"","title":"VeraShackle","type":"jams"}]