Screenshots
-
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. ā©ļø - How do I get the platform starting point?
- How can I actually respawn the platform?
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.

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:

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:

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

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:

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.
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.

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?

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.

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.

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. š

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.

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:

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.

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:
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.
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.

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.

š 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.

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.

Already making daily note archives for 2025.

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

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).
