Screenshots

    My first automation to assist me with this ā€œissue driven developmentā€ approach: a Keyboard Maestro macro which will activate Obsidian, go to the end of the document, and add a new line beginning with the current time.

    Auto-generated description: A configuration window for creating a new timestamped line in Obsidian, detailing trigger options and actions.

    My goal is to have one Obsidian note per Jira task, which I will have open when I’m actively working on it. When I want to record something, like a decision or passing thought, I’ll press Cmd+Option+Ctrl+L to fire this macro, and start typing. Couldn’t resist adding some form of automation for this, but hey: at least it’s not some hacked-up, makeshift app this time.

    Devlog: Blogging Tools — Ideas For Stills For A Podcast Clips Feature

    I recently discovered that Pocketcasts for Android have changed their clip feature. It still exists, but instead of producing a video which you could share on the socials, it produces a link to play the clip from the Pocketcasts web player. Understandable to some degree: it always took a little bit of time to make these videos. But hardly a suitable solution for sharing clips of private podcasts: one could just listen to the entire episode from the site. Not to mention relying on a dependent service for as long as those links (or the original podcast) is around.

    So… um, yeah, I’m wondering if I could building something for myself that could replicate this.

    I’m thinking of another module for Blogging Tools. I was already using this tool to crop the clip videos that came from Pocketcasts so it was already in my workflow. It also has ffmpeg bundled in the deployable artefact, meaning that I could use to produce video. Nothing fancy: I’m thinking of a still showing the show title, episode title, and artwork, with the audio track playing. I pretty confident that ffmpeg can handle such tasks.

    I decided to start with the fun part: making the stills. I started with using Draw2D to provide a very simple frame where I could place the artwork and render the text. I just started with primary colours so I could get the layout looking good:

    Auto-generated description: A date, episode title, and show name are displayed alongside an image of ocean waves against rocks in a colorful border.

    I’m using Roboto Semi-bold for the title font, and Oswald Regular for the date. I do like the look of Oswald, the narrower style contrasts nicely with the neutral Roboto. Draw2D provides methods for measuring text sizes, which I’m using to power the text wrapping layout algorithm (it’s pretty dumb. It basically adds words to a line until it can’t fit the available space)

    The layout I got nailed down yesterday evening. This evening I focused on colour.

    I want the frame to be interesting and close to the prominent colours that come from the artwork. I found this library which returns the dominant colours of an image using K-means clustering. I’ll be honest: I haven’t looked at how this actually works. But I tried the library out with some random artwork from Lorem Picsum, and I was quite happy with the colours it was returning. After adding this library1 to calculate the contract for the text colour, plus a slight shadow, and the stills started looking pretty good:

    Auto-generated description: Six rectangular cards each feature a different background image with the date 14 April 2020, text A pretty long episode title, and My test show.

    I then tried some real podcast artwork, starting with ATP. And that’s where things started going off the rails a little:

    Auto-generated description: Four color variations of a promotional card design featuring a logo with rainbow stripes, a date of 14 April 2020, and text stating A pretty long episode title and My test show.

    The library returns the colours in order of frequency, and I was using the first colour as the border and the second as the card background. But I’m guessing since the ATP logo has so few actual colour, the K-means algorithm was finding those of equal prominence and returning them in a random order. Since the first and second are of equal prominence, the results were a little garish and completely random.

    To reduce the effects of this, I finished the evening by trying a variation where the card background was simply a shade of the border. That still produced random results, but at least the colour choices were a little more harmonious:

    Auto-generated description: A series of four visually distinct cards display a logo, date, episode title, and show subtitle, each set against different colored backgrounds.

    I’m not sure what I want to do here. I’ll need to explore the library a little, just to see whether it’s possible to reduce the amount of randomness. Might be that I go with the shaded approach and just keep it random: having some variety could make things interesting.

    Of course, I’m still doing the easy and fun part. How the UI for making the clip will look is going to be a challenge. More on that in the future if I decide to keep working on this. And if not, at least I’ve got these nice looking stills.


    1. The annoying thing about this library is that it doesn’t use Go’s standard Color type, nor does it describe the limits of each component. So for anyone using this library: the range for R, G, and B go from 0 to 255, and A goes from 0 to 1. ā†©ļøŽ

    Devlog: Godot Game Update

    A brief status update on that Godot game. I think we’re pretty close to a finished 4-1 level. The underground section has been built, and the level has been decorated. I’ve also added a couple of secrets, which needed a few new mechanics — like doorways, which are used to transport the player around the level — plus some refinement to existing ones. I am a little concerned about the amount of waiting involved near the end of the first half, where the player will need to make their way across a large gap by jumping on the slow cycling ā€œlayer 2ā€ tile layer. I’ll see what feedback I get from play-testers about this.

    Auto-generated description: A pixelated, side-scrolling platformer video game scene features a knight character on a cliff with blocks, coins, and a treasure chest in view.

    I’ve also filled in the backdrop of all the levels to date. The grey default background colour has been replaced with a sky made of colour bands that came with the asset set I’m using. It’s basic, maybe too basic, but as long as it doesn’t clash with the interactive elements, and in the interest of ā€œmerely shipping something,ā€ I think it’ll do for now.

    Finally, I made some changes from feedback I got from play-testers. Movement can now be made using WASD, along with the inverted-T arrow keys. I’ve also bound jump to the Enter key for those that want to use WASD. I made some changes in how secrets are telegraphed with the goal of making them more consistent. This is always a bit of a balancing act, as I don’t want to make them too obvious: they’re secrets after all. I added a tiny bit of ā€œslipā€ to the player’s movement, so they don’t stop dead whenever the key is released. I didn’t want to go too far here: adding too much inertia introduced the risk of the player just falling off a platform after landing from a jump, which I don’t think is a great experience. And I fixed a window resizing bug (Godot enables window resizing by default, for anyone else who needs to know this).

    I think the next thing to do is to organise another version for play-testers, just to gauge what they think of level 4-1. Then it’s either on to level 4-2, or even introduce a new level between 1-1 and 1-2 to reduce the ramp up in difficulty between the two levels. Fortunately I’ve got some ideas on what I want to do for both of theses, so they’ll be easy starters. I’ve still got nothing for level 2-1, but I have a few ideas on what I want to do for 2-2, so maybe I’ll start that too.

    Does Vivaldi Mobile for Android have GIF support? Yes… I guess? Not entirely sure who’s asking. Or why. Are these the same people who want to know if Vivaldi has paid stickers or file sharing support? Is this for a hypothetical messaging app?

    Auto-generated description: A review screen for the Vivaldi Browser app asks the user to rate the app with stars and provide feedback, with additional questions about GIF support.

    Blessed be the Mail.app View menu and the option to hide the useless Apple AI priority messages. My Inbox is now slightly more sane.

    Auto-generated description: A computer screen displays a dropdown menu with various options such as Show Tab Bar and Show Priority, against a background of tall trees.

    A bit more on the Godot game this morning, this time working on background tiles artwork. Made some grey masonry tiles for the end castle sequences. Also tried some background rocks for underground areas. I’m pretty rubbish at anything organic, but they didn’t turn out too bad.

    Auto-generated description: Two rectangular pixel art frames with stone textures, one in brown and the other in gray, are displayed with matching filled versions inside them.
    Right side has the background tiles surrounded with their complementary foreground tiles on the left.

    It pains me that Forgejo’s CI ā€œpipeline runningā€ animation spins anti-clockwise, as if you’re going backwards in time. A metaphor, perhaps? Services get undeployed, binaries go back to the source code, projects return to their seeds of ideas. šŸ¤”šŸ’­

    Oh, build’s done. Never-mind. šŸ˜€

    A commit titled 'Added the grid image processor' by user 'lmika' was pushed to the main branch with the workflow file 'deploy.yaml', with a yellow spinner spinning in an anti-clockwise direction.

    Was looking at how I could add hazards to my Godot project, such as spikes. My first idea was to find a way to detect collisions with tiles in a TileMap in Godot. But there was no real obvious way to do so, suggesting to me that this was not the way I should be going about this. Many suggested simply using an Area2D node to detect when a play touches a hazard.

    I was hesitant to copy and paste the scene node I had which handled the collision signal and kill the player — the so-called ā€œkill zoneā€ scene —but today I learnt that it’s possible to add multiple CollisionShape2D nodes to an Area2D node. This meant I needed only a single ā€œkill zoneā€ scene node, and just draw out the kill zones over the spikes as children. The TileMap simply provides the graphics.

    Auto-generated description: A game development interface displaying level design with a grid layout, tiles, and collision shapes is shown.

    This discovery may seem a little trivial, but I’d prefer to duplicate as few nodes as a can, just so I’ve got less to touch when I want to change something.

    Tried opening my Godot project this morning and was greeted with the following error:

    Auto-generated description: A computer screen displays a development environment with an error message pop-up about an invalid or missing scene file.
    scene/resources/resource_format_text.cpp:284 - res://scenes/falling_platform.tscn:14 - Parse Error: 
    Failed loading resource: res://scenes/falling_platform.tscn. Make sure resources have been imported by opening the project in the editor at least once.
    Failed to instantiate scene state of "res://scenes/falling_platform.tscn", node count is 0. Make sure the PackedScene resource is valid.
    Failed to load scene dependency: "res://scenes/falling_platform.tscn". Make sure the required scene is valid.
    

    Traced it back to the technique I was using to respawn the falling platform. Looks like Godot didn’t like the two preloads I included:

    # file: scripts/falling_platform.gd
    
    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    

    This resulted in a parse error and Godot thinking the level scene was corrupted. In retrospect, this kinda makes sense. What I doing was technically a circular dependency, where the scene and script was trying to preload itself. I was hoping Godot was smart enough to recognise this, but I guess not.

    So I had to change the respawn code. I modified it to make use the duplicate method. Here’s the revised version:

    func respawn():	
        var dup = self.duplicate()
        dup.global_position = init_global_position	
    
        # Duplicate also copies the velocity so zero it out here
        dup.velocity = Vector2(0, 0)
        get_tree().current_scene.add_child(dup)
    

    After some limited testing, this seems to work. One good thing about this approach is that it looks like duplicate copies the script, so I no longer need to do anything special here.

    So I guess the lesson here is don’t try to preload the script within itself.

    Adventures In Godot: Respawning A Falling Platform

    My taste of going through a Godot tutorial last week has got me wanting more, so I’ve set about building a game with it. Thanks to my limited art skills, I’m using the same asset pack that was used in the video, although I am planning to add a bit of my own here and there.

    But it’s the new mechanics I enjoy working on, such as adding falling platforms. If you’ve played any platformer, you know what these look like: platforms that are suspended in air, until the player lands on them, at which point gravity takes a hold and they start falling, usually into a pit killing the player in the process:

    The platforms are built as a CharacterBody2D with an Area2D that will detect when a player enters the collision shape. When they do, a script will run which will have the platform ā€œslippingā€ for a second, before the gravity is turned on and the platform falls under it’s own weight. The whole thing is bundled as a reusable scene which I could drag into the level I’m working on.

    Auto-generated description: A game development interface with a sprite and code editor is shown, from a software environment like Godot.

    I got the basics of this working reasonably quickly, yet today, I had a devil of a time going beyond that. The issue was that I wanted the platform to respawn after it fell off the map, so that the player wouldn’t get soft-locked at at an area where the platform was needed to escape. After a false-start trying to reposition the platform at it’s starting point after it fell, I figured it was just easier to respawn the platform when the old one was removed. To do this I had to solve two problems:

    1. How do I get the platform starting point?
    2. How can I actually respawn the platform?

    Getting The Starting Point

    A Node2D object has the property global_position, which returns the position of the object based on the world coordinates. However, it seems like this position is not correct when the _init function of the attached script is called. I suspect this is because this function is called before the platform is added to the scene tree, when the final world coordinates are known.

    Fortunately, there exists the _ready notification, which is invoked when the node is added to the scene tree. After some experimentation, I managed to confirm that global_position properly was correct. So tracking the starting point is a simple as storing that value in a variable:

    var init_global_position = null
    
    func _ready():
    	init_global_position = global_position
    

    Another option is to use the _enter_tree() notification. From the documentation, it looks like either would probably work here, with the only difference being the order in which this notification is invoked on parents and children (_enter_tree is called by the parent first, whereas _ready is called by the children first).

    Respawning The Platform

    The next trick was finding out how to respawn the platform. The usual technique for doing so, based on the results of my web searching, is to load the platform scene, instantiate a new instance of it, and added it to the scene tree.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        add_child(dup)
    

    Many of the examples I’ve seen online added the new scene node as a child of the current node. This wouldn’t work for me as I wanted to free the current node at the same time, and doing so would free the newly instantiated child. The fix for this was easy enough: I just added the new node as a child of the current scene.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    I still had to reposition the new node to the spawn point. Fortunately the global_position property is also settable, so it was simply a matter of setting that property before adding it to the tree (this is so that it’s correct when the newly instantiated node receives the _ready notification).

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    This spawned the platform at the desired positioned, but there was a huge problem: when the player jumped on the newly spawn platform, it wouldn’t fall. The Area2D connection was not invoking the script to turn on the gravity:

    It took me a while to figured out what was going on, but I came to the conclusion that the packed scene was loading properly, but without the script attached. Turns out a Script is a resource separate from the scene, and can be loaded and attached to an object via the set_script method:

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.set_script(FallingPlatformScript)
    
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    Finally, after figuring all this out, I was able to spawn a new falling platform, have it positioned at the starting position of the old platform, and react to the player standing on it.

    The time it took to work this out is actually a little surprising. I was expecting others to run into the same problem I was facing, where they were trying to instantiate a scene only to have the scripts not do anything. Yet it took me 45 minutes of web searching through Stack Overflow and forum posts that didn’t solve my problem. It was only after a bit of experimentation and print-debugging on my own that I realised that I actually had to attached the script after instantiating the node.

    To be fair, I will attribute some of this to not understanding the problem at first: I actually thought the Area2D wasn’t actually being instantiated at all. Yet not one of the Stack Overflow answers or forum post floated the possibility that the script wasn’t being loaded alongside the scene. This does suggest to me that my approach may not be optimal. There does exist a ā€œLocal to Sceneā€ switch in the script inspector that could help, although turning it on doesn’t seem to do much. But surely there must be some way to instantiate the script alongside the scene.

    Anyway, that’s for later. For now, I’m happy that I’ve got something that works.

    šŸ”— cathoderay.tube

    Auto-generated description: A web browser window displays the word computers!!! in large text on a plain white background.

    Yes.

    Running PeerTube In Coolify

    A guide for setting up a basic PeerTube instance on Coolify using a docker-compose file.

    Gave Dave Winer’s Wordland a try today. I like it. It’s quite a nice writing environment to work in. Wrote a couple of long form posts in it, along with a few that were a paragraph or two, and the editor felt great to use. Does a good job growing with the length of the piece too.

    Auto-generated description: A text editor displays a guide titled How To Start A Naming Convention, discussing strategies for naming conventions in technical environments.

    Making A Small Two-Letter Country Code Lookup Page

    A small evening project where I made a simple site designed for Vivalidi’s sidebar to quickly lookup two-letter country codes defined in ISO-3166-1 alpha 2.

    Dusted off Podcast Favourites (last commit 25 April 2022) and fixed a longstanding issue of thumbnails being lost when they’re changed in the feed. Editing the feed properties will now force a refresh of the thumbnail URLs. Didn’t need to change anything else, which was a nice change.

    Auto-generated description: A podcast favourites list showing episodes with titles and descriptions from the ATP - Members Feed.

    šŸ”— Prefer Numbered Lists to Bullets

    Good arguments for using numbered listed instead of bullets in chat communication. I don’t disagree with any of them. I will say that tend to preferred bulleted lists simply because the chat apps I use tend to make using numbered lists more difficult than it should be. Slack, for example, only starts a ā€œrealā€ numbered list when it detects you type 1.. And once you’ve started, there’s no way to skip ordinals within the same numbered list.

    Auto-generated description: A chat message from Leon Mika lists items with different numbers and includes a section to jot something down.
    Note that "1. This" the only "real" numbered list, and has a different appearance.

    Even Obsidian’s implementation is not perfect. Despite making it easy to start a numbered list at an arbitrary ordinal, it’s still not possible to skip ordinals.

    It’d be simpler if they didn’t try to automatically make ā€œrealā€ numbered lists at all.

    Via: Jim Nielsen

    Trying out Bayou theme by @Mtt on a test blog. Lots to like about it, especially the idea of having the latest micro-post appear in the form of a status message. Very unique.

    Auto-generated description: A minimal blog page titled Leon Mika features categories like About, Archive, and Replies, along with a post mentioning categories being sorted and three other listed posts from 4 February 2025.

    Already making daily note archives for 2025.

    Auto-generated description: A computer interface displaying a folder structure for daily notes from 2022 to 2025, highlighting a note dated January 6, 2025.

    Okay, I think I know why I stopped playing Wordle.

    Auto-generated description: A screenshot of a word-guessing game shows the word TOCK in progress with several guessed words, color-coded hints, and navigation buttons.

    Finding that styling a page with min-height: 100vh causes the need to scroll when I open the page in Vivaldi Mobile, as vh does not recognise vertical space taken up by toolbars. What I actually want is 100dvh (i.e. dynamic view-height) which does. Found this slide helpful (source and via).

    Auto-generated description: Three smartphones display different viewport height measurements labeled as dynamic, largest, and smallest, with Google's branding in the corner.
Older Posts →
Lightbox Image