2026-05-10 Type It All devlog
Two monster tickets finally dealt with
published: Sun 10 May 2026There were two ugly tickets hanging over my head for a while: challenge word highlighting and fuzzy search. Both have now been implemented, but with a bunch of caveats and concessions.
I fought with the graphics shaders for challenge words for more than 10 hours before I finally gave up and just styled a capsule behind the word, rather than the text itself. This is due to a combination of issues inherent to Godot, namely how it reports the texture size and UV coordinates to the shader code. The especially annoying part is that all text in a CanvasGroup is pre-drawn to a single texture before being passed to the shader (but not necessarily in the same orientation/position as it should be), UV coordinates are given with respect to the full texture, and then, after whatever shader changes are applied to the full texture, Godot cuts out the original size of the letter (ignoring vertex size increases) and places it in the letter's correct position. This results in bizarre behavior when your shader draws outside of the bounds of the letter itself. There were also issues with the capsule shader (namely, it uses a texture size of 1x1 always), but those were more easily overcome.
The capsule shaders are not finalized and will definitely need tweaking, but they look half-decent now and the red word finally has spikes (and the blue word pulses).
The other bugbear was fuzzy text searching. There are dozens of algorithms out there, but the majority only search for presence within a given target string, whereas I want the location within the target. So I went with a brain-dead recursive algorithm. It is not performant, but a number of tricks allow it to handle my needs for now. The big blocker on this one was that short typed strings (e.g. <10 words) would work fine, but typing more would start causing hiccups and single-digit FPS by the time I hit 20 words typed. I eventually figured out that this was because it was keeping every possible matched string with missing letters. So typing "apple" would store _pple, a_ple, ap_le, app_e, and appl_, even if the full word apple also matched the target text. I couldn't just discard "worse" matches, because I had to feed previous results into the algorithm to save on compute time and discarding "worse" matches could result in ignoring valid results later on. I got around this by discarding results which had the same starting target index (e.g. that first "a") but a worse skip count. So the algorithm now keeps apple and _pple. Not perfect, but good enough for now. Unfortunately, this might have unforeseen/undesired/unexpected consequences for edge cases, but I'm not going to worry about that for now.
The number of errors "allowed" by fuzzy searching is configurable in the options as the "difficulty."
Finally, I implemented two features which were deliciously simple in comparison: a !ifreplacelist text command that builds a textual list based on individual conditions and then joins them with the correct grammatical features (e.g. a simple "or" for two present items or an Oxford comma for three); and cursors indicating the last typed latter, a feature requested by Matthew.