Azhdarchid

I'm Bruno Dias, a São Paulo-based designer (systems, narrative, mechanics) and writer working in video games. Azhdarchid (named after the family of large pterosaurs) is my personal blog; I also have a more professional landing page at brunodias.dev.

You may know me from such video games as Where the Water Tastes Like Wine, Neo Cab, Pathologic 2 and 3, or Fallen London—you can see my full credits on MobyGames. I also edit the Game Narrative Reader, a link anthology of great essays about video game narrative design on the web.

For my sins, you can find me on Bluesky (@brunodias.dev) and on the Fediverse (@bruno@azhdarchid.online). For my cinema sins, I'm also on Letterboxd (notbrunoagain).

Recent Posts

Jump to full post list

GDScript: The Good, Bad, and Ugly Parts

I recently finished writing a fairly significant amount of code—implementing a system first in TypeScript, and then porting that same system to GDScript so it can run on Godot (I have a reason to need both). This has given me a really thorough impression of many aspects of GDScript as a programming language.

I've said in the past that I think if you're going to use Godot—which is certainly the best engine for a lot of projects now, and definitely a strict improvement from Unity unless you are very invested in that ecosystem—the best thing you can do is give GDScript a shot. I think a lot of us have recollections of things like UnityScript or even ActionScript, which may make one assume that GDScript is a "toy" language. It really isn't; it's definitely svelte in terms of features—closer to Lua than to JavaScript—but it's very far from being underbaked, and it has a lot of selling points—as well as some caveats.

The Good

GDScript is designed specifically to be used as the high-level language for implementing game logic in a video game. This is something that's rather unique; virtually every other language used for game development was originally intended to solve other problems: Lua for industrial automation, C# for taking over Java's niche in server applications and enterprise software, JavaScript for enabling supply chain attacks, C++ for getting rid of your remaining toes, etc.

This means that GDScript has some useful features that are missing from other high-level languages. It has native Vector2 and Vector3 types. Unlike JavaScript, it distinguishes integers and floating-point numbers. The standard library includes things like an actually-useful PRNG (again, unlike JavaScript). It has a match statement that's inherently very useful for implementing complicated if-then-else behavioral logic of the kind video games have all the time. Unlike Lua, it natively supports packed arrays. The standard library includes many functions that are widely used in game dev: lerp(), smoothstep(), wrap(), etc.

It's also very tightly coupled to the engine in ways that simply speed up development. Godot's system of annotations mean that it's trivial to write a node class that comes with an editor panel that allows for easily configuring objects in the editor. The semantics of reading resource files at runtime are made much simpler and more consistent through preload() and load(); preload() is notably something you can't really get with other languages in Godot.

GDScript's treatment of signals as a first-class concept is incredibly useful for most game programming; "thing that has to hear about it when other thing does something" is one of the most common requirements for implementing any kind of game system.

The language also has no garbage collector, which is extremely unique among high-level languages. I think the Jon Blowesque position that all automatic memory management is bad is silly. But it is true that GCs have somewhat unpredictable performance characteristics and tend to create stuttering, which is very much not ideal for a video game.

GDScript instead uses a combination of manual memory management and reference counting. Manual memory management is actually the norm, and it's something that 90% of the time you don't have to think about; most objects that get allocated in Godot are scene tree nodes, and nodes simply free themselves when they leave the scene tree (after freeing their children). The assumption "an object attached to the scene tree needs to stay in memory, an object out of the scene tree can go away" is obviously very useful in the context of this kind of game engine, but it's something you can't really use to your advantage without using a language that has manual memory management as a default.

Reference counting on the other hand is dumbly simple, and is the default for objects that don't exist as scene tree nodes. It does require a marginal amount of thinking about it; Godot does not implement some general way of solving cyclical references, which are the general problem with reference counting. But does provide a weakref() function, so it's very easy to let dependent objects store weak references to their parents on the assumption that they, too, are just going to free when the parent frees and thus kills their last reference.

C# has more language features outright but losing out on both the tight integration to the engine, and having C#'s garbage collector running in the background, are both significant downsides.

Finally, this isn't really an upside of the language itself, but using GDScript lets you use Godot's debugger, which is an extremely useful tool.

The Bad

GDScript is a dynamic language with incremental typing—meaning you can write type hints to get some compile type checking. This feature is underbaked, basically unfinished, and writing statically-typed code in GDScript is kind of a halfway affair. Consider the following code:

var doubled_str = function (n: int) -> String: return str(2 * n)
var numbers: Array[int] = [1, 2, 3]
var words: Array[String] = numbers.map(doubled_str)

You'd expect that words would then contain ["2", "4", "6"]. But no, this code actually throws a type error, because map(), no matter what, returns an untyped array and GDScript can't assign that to the typed array variable. You have to manually cast the array by running it through the typed array constructor:

var words = Array(numbers.map(doubled_str), TYPE_STRING, "", null) 

...but this, as far as I can tell, doesn't actually work with things like inner classes; it's both verbose and kind of dumb, and is downstream of the fact that functions, in GDScript, don't have specific types. In most languages with good type systems, you can specify a type like (eg, TypeScript):

type NumDescriber = (n: number) => string

Functions, when passed around as values, have a type that reflects their arguments and return value. Map, for example, looks something like this:

function map<T,U> (cb: (elem: T) => U) => U[]

GDScript has no conception of this at all; every function is just a generic Callable object. Typing for composite types is extremely limited in general; while you can specify the key and value types for a dictionary (hash map), you again can't specify inner types for nested objects. You can't say, eg, Array[Array[int]]; the language is generally geared towards the idea that any data structure more complicated than a very simple hash map should be its own class and handle its data in an OOP way, which isn't always appropriate. Many structs that in the TypeScript codebase are interfaces have, in the GDScript port, become classes.

GDScript's static typing system is analogous to TypeScript's in function; it's a way of adding compile-time checking as a layer on top of an otherwise dynamic language. But TypeScript has a lot of sophisticated "narrowing" logic to determine the type of values at different points in the code based on cues like if (typeof x === "string"). GDScript's narrowing logic is nowhere near as smart.

There are no union types or discriminated unions, which has been a very noticeable limitation. The TypeScript project I ported relies heavily on unions of primitive types; in GDScript, all of those become Variants, and there's really no hope of doing anything else with them unless I want to marshall them into some sort of container class, which wouldn't really address the issue and would create large numbers of pointless heap allocations.

Generally speaking, I don't think you can write statically-typed GDScript, not really; the dynamic nature of the language always creeps in. I'd actively suggest not using typed collections in most contexts, because the feature is overall fairly underbaked. In most places where a typed collection makes sense, you'll probably be using a packed array anyway.

The type system is really the one aspect of GDScript that I think is both 1. bad, 2. can be improved on. A nice thing about GDScript's nature as a language that's joined at the hip to the engine is that it doesn't have to care about backwards compatibility; you're never going to try to run your Godot 4.x projects in Godot 5.0, so GDScript 5.0 could break compatibility with any number of new features or breaking changes. This makes the language substantially more "fixable" than most languages, just as a practical matter, so I do have hope that it could evolve.

The Ugly

By "the ugly parts", I mean things that I think are bad out of personal preference—or just things that are warty and incomplete.

Mostly, I have gripes with GDScript's syntax. I dislike indentation languages out of principle, for one thing; I don't think the extra steps needed to parse them are worth not having to type } or end, and I find the overall concept fussy. They also tend to make any kind of nested structure confusing. Writing a callback that's more than one line long in GDScript is pure gore:

ary.map(func (e: String) -> int:
    # ...do various things with e...
    return e)

The syntax is overall verbose in unpleasant ways all over, sometimes because it's missing useful syntactic sugar from other languages. Lambdas in general are fussy; js arrow syntax gives (a ,b) => a + b while the equivalent in GDScript is func (a, b): return a + b, nearly twice as long. GDScript doesn't have destructuring assignment like js (let {id, mass} = item), nor Lua's multi assignment (local x, y += dx, dy).

Working with dictionaries is made slightly less unaesthetic by GDScript's support of both JSON-style ({"key": "value"}) and Lua-style ({ key = "value" }) style, but I still long for js's structuring assignment construct, where if you have local vars named foo and bar, you can just write return { foo, bar } and it does what you think it does.

GDScript also lacks a spread operator, so constructions that are func(...args) in js become the gory func.callv(args) in GDScript. The language enforces this idea that you can call a function that's in scope, but a function object you get as a value can only be called through the .call() and .callv() methods on Callable objects, which feels annoyingly fussy.

You can't define inner functions either—you have to assign a lambda to a variable and then .call() that.

It generally lacks language constructs for working with immutable objects. In js code that's meant to be stateless, you write a lot of things like:

return {
    ...oldStruct,
    id, weight, timesUpdated
};

While this kind of thing isn't always the most performant, it's a very useful tool; GDScript really doesn't play nice with this pattern, for many reasons.

The combination of the janky type system, annoying-to-use lambdas, aversion to immutability, and generally fussy approach to writing any sort of higher-order function combine to make any sort of functional programming unnecessarily frictional in GDScript. The language is, understandably¸ mostly geared towards an OOP-procedural model of programming and that works well enough for a lot of problems. But not every problem. I'm very used to true multi-paradigm languages where you can dip into a functional style where it makes sense, which it often does! Many computations are better expressed through map() and reduce(). GDscript's arrays don't even provide flatmap(), let alone zip().

This is all stuff that's not really as much of a blocker as the type system. The type system really is in a state where people who want static types are not going to be satisfied with it, while people who think static typing is a sort of dog cone for programmers are going to find it chafing. Not having flatmap or slightly fussy syntax, you can live with—though, again, the language can change in breaking ways for Godot 5. Just adopt Lua-style syntax. Bring back end. You know you want to. The language even lacks ++ and -- operators, a feature inherited from Lua (where they'd clash with -- being the comment syntax) even though GDScript's comments start with # like in Python!

The Flavors of AI Guy You See on Bluesky

The Flavors of AI Guy You See on Bluesky

I spend a lot of time on Bluesky, for which reason my soul will surely meet oblivion in the jaws of Ammit; but while I'm there I see a lot of AI boosterism. Here's a taxonomy.

Type A: The True Believer

Typical phrases: "AGI is already here, it's just not evenly distributed", "singularity", "the stochastic parrots paper has been disproven"

Favorite AI exec: Sam Altman

This is the original hard core of AGI belief. Full on TESCREALism with explicit belief in "machine consciousness". These guys feel rare nowadays, less so perhaps because they're going away and more because they are numerically overwhelmed by the other flavors of AI guy.

Type B: The Business Idiot

Typical phrases: "Yeah I don't read the PRs anymore, I just merge 'em"; "if you're not using agents to code, you're just slowing everyone else down"; "NGMI"; any excited discussion about which models work best for which tasks, which harnesses they're using, etc.

Favorite AI exec: Satya Nadella

This is where most of Bluesky's core team falls; Paul "not using AI is like getting on the highway in a horse cart" Frazee is a good exemplar. The business idiot's core proposition is that vibe coding is 1. the future of software development, 2. going to turn your $100m SaaS app idea into a reality in a week. Basically fully in the throes of token anxiety.

Type C: The Little Guy Enjoyer

Typical phrases: "Guy who lives in the computer"; "hungry ghost in a jar"; "clanker is a slur"; "democratizing creativity".

Favorite AI exec: Dario Amodei

Talked to a chatbot too much and went soft in the brain pan. Thinks Claude is real, and strong, and their personal friend. Very concerned that you like and respect LLMs, which again are their friends. Actively invested in anthropomorphizing those systems.

Type D: The Fake Pragmatist

Typical phrases: "It's not going away"; "it already outperforms humans at writing code"; "it can only get even better"; "you should listen to experts when they say it's useful"; "data center panic is a glonzo issue";

Favorite AI exec: Would not cop to having one, but it's Dario Amodei again

Not only do they think AI is useful, they are very invested that you 1. agree, 2. stop asking any inconvenient questions about long-run effects or externalities, 3. generally lie down and take it. Heavily overlapping with bluesky's cohort of bloodless quants. Liable to abuse the term "wonk". Thinks LLM use can solve things like unemployment benefits bureaucracy or transit because they haven't thought about it for more than fifteen seconds. Uses ChatGPT "just a little" to "suggest edits" to their Liberal Currents posts.

A ★★★ review of Coyote vs. Acme

A ★★★ review of Coyote vs. Acme
The poster for Coyote vs. Acme (2026

Reviewed by Bruno Dias

2026 · ★★★

  1. in this age of resurrected AI celebrities idk how I feel about cartoon peter lorre
  2. the animation in this movie is a brave effort but cel shading on 3d animation just does not hit the same
  3. is peter lorre/granny an example of the weirdest guy you've ever met/astonishing baddie dynamic
  4. good to have canonical confirmation, after decades of queerbaiting, that wile e coyote and the roadrunner are together in some kind of depraved kink situation
  5. john cena should only act in movies opposite cartoon characters or muppets, from here on out
  6. really I think to properly understand this movie you have to dig deeply into the Lacanian concept of jouissance

A ★★★ review of Dead Man's Wire

A ★★★ review of Dead Man's Wire
The poster for Dead Man's Wire (2025

Reviewed by Bruno Dias

2025 · ★★★

What an odd little movie. Much more trying to be just a fun thriller than you'd expect. You really think this is going to be some kind of "oh this guy is doing Saw for real" thing but really, it's mostly a character study of a deeply strange and lonely guy doing a depely strange and lonely thing. As a movie it has no real vitriol for anybody—excepting perhaps Pacino's comically heartless and self-important character—but it's not interested in romanticizing its subject either. Feels very much like the intended application of this movie is that you just watch it and go "oh yeah, the 1970s were craaaaazy" and then go get dinner.

I wrote about Pirates! for Remap

Pirates was something different. It's hard to pin it down to a genre even today, but it certainly wasn't like previous Meier titles—even though it's the first game to be called Sid Meier's Something or Other. At the time the game came out in 1987, it was generally called an "action adventure" game; this is somewhat hard to square with the way most people today would understand the genre.

I wrote a retrospective of Sid Meier's Pirates! for remap, discussing that game's place in the history of game design and why it remains a unique achievement even today. I'm very happy with this piece; I feel like since coming back to this kind of nonfiction writing, I've started to stretch more and more what I do in this space, and I've had a great time doing it.

If you only follow me through the RSS feed for this blog you might not have seen it either, but I have written several things for Remap this year, including:

All Posts