More from Alice GG
In April, the Godot foundation organized a conference close to their home base: GodotCon Amsterdam. It was a good occasion for us to showcase Paradox Tower and get some beta feedback from other developers. Most importantly it was two entire days of talks about game development in Godot. Here are some of the ones I recommend checking out if you are into it. 1000 rigged AnimationPlayers in 3D, impossible? Having a lot of animated 3D objects in Godot can seriously damage performance. This is something to look for if you’re developing for systems with limited resources, which may rely on the Compatibility renderer (like Android or Switch). Bertrand Bordage explains how to get around this limitation using Vertex Animation Textures. It basically consists of baking an animation into a shader. I had previously never heard of it and this talk really seems like a doable solution for Switch ports of 3D games. The Clone Wars: Defending Godot Games From Reupload Scams Godot games are very easy to reverse engineer. Many developers assume this is not an issue and will only be used by modders and curious players. Sadly, there’s a cottage industry of shady publishers who will take advantage of this to copy your game. I’ve previously talked a bit about technical countermeasures you can use to make this process harder. In this talk, Yasen Dinkov also explores the legal and marketing aspects of the problem. He provides ideas that can make taking down illegal store pages much easier. From model to gameplay in seconds: setting up your Blender Godot pipeline Doing a lot of manual steps can kill your productivity. A big source of this is manually importing 3D assets and setting them up as Godot scenes. Godot comes with a set of tools to speed that up: post-import scripts. Here, Marion Allard explains how to make the best use of it, with step-by-step examples using Blender objects: Make your own fractal! A more relaxed talk to finish, with a short and fun introduction to fractals by Marta Imke. It explores visual math and the different ways you can build fractal images out of simple functions. This is definitely something that can be useful if you want to create some trippy visual effects.
In the latest Steam client update, Valve unveiled major changes to its store’s home page. One of the main difference is that the “Popular Upcoming” section, which shows upcoming games now shows a lot fewer games. This seems to worry indie developers who relied on this section as a major driver of visibility around launch. I personally think this is will not change much to how indie games are marketed and might even be a positive for many niche developers. Popular upcoming is dead Now that game development is more accessible than ever, and anyone can post their games on Steam and Epic. Since all those new developers need to market their games, this has created a cottage industry of game marketing influencers. The strategy they teach is basically always a variant of: Make a game (and a demo) Get a lot of people to play the demo during Steam Next Fest Get 6000+ Wishlist Appear in Popular Upcoming ??? Profit Since everyone is running around with approximately the same strategy, it became a very crowded trade. With everyone publishing a more and more polished demo, Next Fest began to have diminishing returns for most developers. In the same way Popular Upcoming was very impactful when maybe 1 or 2 games appeared in it every day, but if it shows a dozen game, it’s not a guarantee of success anymore. And let’s be honest, players have probably been paying less and less attention to this section of the store for a while. Basically every shovelware publisher knows how to game the algorithm and get their new version of Slop Simulator 2027 in their. Long live the Personal Calendar In the same update, Valve introduced a new feature: the Personal Calendar. It’s a whole page which aims at making players discover games that are personalized to their taste. The recommendation algorithm seems to work somewhat decently. I do not think it is entirely based on tags, since it seems to recommend me a lot of “Open World Survival Craft” games, which is not something I usually play. It may be using wishlist behavior of similar players to make recommendations. One of the major differences is that it shows a calendar for the next eight weeks, so games can have a much longer visibility window. If a player of your target audience didn’t browse Steam on the day before your launch, it would miss you on popular upcoming, while now they can It also shows recently released games, in the past 7 days and past month. Contrary to the homepage “Popular New Releases” tab (ex “New & Trending”), which requires a lot of active players, this section seems accessible even to unknown games. Steam is currently recommending me Imago Season with at the moment 0 review and only 2 concurrent players. A new era for niche games? Before this change, it was extremely difficult for developers to market niche games. I know it from first-hand experience, since I have no doubt Dice ‘n Goblins would have been easier to market if it was not the weirdest combination of ideas possible. Because of this, a lot of new indie game developers were advised to stick to a very popular genre. You love real-time strategy? Too bad, you’ll have to make an action roguelike instead. This has led to a loss of creativity in the indie game world. With many developers working on genres that do not interest them, using recycled ideas and aesthetics. I do believe that this new version of Steam might swing the pendulum in the other way. Now that players of niche games can get recommendation customized to their taste, making games for a non-mainstream audience can become viable again.
Lately, I’ve been experimenting with building games in Ebitengine. It’s a 2D engine that lets you create games using the Go programming language. To be able to iterate faster on visual effects, I have created a tool that lets me preview shaders: Luluka. Kage Shaders For those who feel like they missed an episode, shaders are small programs running on the GPU which can be used to modify the pixels of an image. In games, they are used everywhere to control how a game looks. They can be used create special effects like blurring an image or making your screen look like a CRT from the 90s. Ebitengine has its own shader language called Kage. It is very convenient because it has a syntax extremely close to Go. Close enough that you can even run go fmt to format your Kage files. Under the hood, Ebitengine will automatically convert Kage shaders into a format understandable by the GPU. Quasilyte’s article about Ebitengine shaders is generally the most comprehensive introduction to the subject. You can also learn more about them in the Kage’s desk. Luluka One of the main difference between Ebitengine and other game engines like Godot, is that it doesn’t have a visual editor. This is generally fine, since I’ve spent most of my career avoiding What-You-See-Is-What-You-Get tools in favor of staying inside NeoVim. But for shaders, you can end up spending a lot of time tweaking a few variables until you get them to look right. That’s why I decided to build my own tool that would let me work on a shader in isolation, and quickly change the variables we give it. You can install Luluka using the following command: go install github.com/Tsukumogami-Software/[email protected] Run a shader by pointing it straight to the file, passing textures with -i and uniform values with -u: luluka sample/transition.kage -i image2.png -i image.png -u Steepness:80 -u Seed.0:15 -u Seed.1:100 -u Seed.2:5000 -u Seed.3:5000 -u Speed:0.08 For more convenience, you can use a YAML file to pass your uniform values. This is especially practical when working with arrays or matrices, since commands can get very long: Steepness: 80 Seed: [15.0, 100.0, 5000.0] Speed: 0.08 luluka sample/transition.kage -i image2.png -i image.png -v values.yaml
Godot games are known to be easy to reverse engineer. Simple tools can extract assets and source code from the packaged files. If you are making commercial games you probably want to take some steps to avoid this. Jorien Loman Godot RE Tools The most popular Godot reverse engineering software is gdsdecomp aka Godot RE Tools. It’s very multi-platform, simple to use, and even comes with a GUI. In a few clicks, you can “Recover” a project from an executable or .pck file. Gdsdecomp is able to find back the file structure of the project, every asset you exported, and the source code with full variable and function names. This is actually really well-made software. It even comes with convenient utilities for people who want to patch translations (one of the many use cases of reverse engineering games). Encrypting .pck files First let’s preface this section with a warning. There’s no way to 100% guarantee that it’s going to be impossible to decompile a game, aside from never distributing the executable. The only thing we can do is make reverse engineering more difficult and time-consuming. Anyway, the recommended way of protecting Godot games against reverse engineering is to encrypt the files inside it. This is done using AES-256, and requires compiling custom export templates. You can find the details in the official docs, but the general idea is this: Clone the Godot source code: git clone [email protected]:godotengine/godot.git Generate an AES-256 key (32 bits in hex format): openssl rand -hex 32 > godot.gdkey Put this key in your environment variables: export SCRIPT_AES256_ENCRYPTION_KEY=$(cat godot.gdkey) Compile the new export templates (for wasm in this example): scons platform=web target=template_release && scons platform=web target=template_debug Set the new templates as custom templates in your export: In your export encryption settings, check “Encrypt Exported PCK”, “Encrypt Index”, and do not forget to put the files and folders you want to encrypt in “Filters to include”: Generate an IV (you should use a new one every export): openssl rand -hex 16 Last but not least, set your AES key and IV in the encryption settings page. If you did everything correctly, your exported .pck should not be readable by gdsdecomp without knowing the key. At the same time, players should still be able to play the game as usual without issues or needing to know what AES-256 means. Sadly, the key is stored in plain text in memory, and it is not very hard to find it there. According to an estimate I just made up, it would take a 12 years-old with an hex editor and a YouTube tutorial around 15 minutes to get the key. There’s even a tool that promises it can find it in only 50 ms. Godot-Secure To solve this problem, we need to obfuscate the decryption process a little bit. For those that feel this sounds terribly complicated, there’s a script called Godot-Secure that was built to help you with that. It will modify Godot source code to significantly alter the decryption process. Instead of directly using the key we store in memory, it will use it and a secret token to derivate a second key and decipher the files with it. In addition, to that, it will change a few magic numbers and can switch the algorithm from AES-256 to Camellia-256. Once you have run the script, you will need to recompile both the export templates and the editor. This is because the editor is responsible for encrypting the files during export. After exporting a file with the secured Godot, the attackers can still easily obtain our key from the binary files. However, this key is useless by itself. They will also need to find the secret token, work through the key derivation method and re-implement the decryption algorithm. This can take a lot of time and requires actual programming knowledge. Improving the obfuscation Of course, if you want to play around with some C++, you can make this a bit more robust by adding custom logic of your own. There are two files that will be relevant to you. First one is core/io/file_access_encrypted.cpp. It contains the encryption logic in the function FileAccessEncrypted::open_and_parse and the decryption logic in the function FileAccessEncrypted::_close. CryptoCore::AESContext ctx; ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption! ctx.decrypt_cfb(ds, iv.ptrw(), data.ptrw(), data.ptrw()); The second is core/io/file_access_pack.cpp. This one contains how the key is loaded from memory in the PackedSourcePCK::try_open_pack function and FileAccessPack constructor. Be careful when modifying this part, as changes with how you load the key will need to be reflected in how you set the key from the editor. Vector<uint8_t> key; #ifdef TOOLS_ENABLED if (!p_decryption_key.is_empty()) { ERR_FAIL_COND_MSG(p_decryption_key.size() != 32, "Decryption key must be 256-bit."); constexpr uint8_t empty_key[32] = {}; if (memcmp(script_encryption_key, empty_key, sizeof(empty_key)) == 0) { key = p_decryption_key; } } else #endif { key.resize(32); memcpy(key.ptrw(), script_encryption_key, 32); } The script_encryption_key variable itself is set at compile time by the script core/core_builders.py.
More in programming
A decade ago, a little bit of history was made. I didn't realize it, but a colleague made a great point, one of those real mind-changing points that seem too obvious to admit same-day. But, the next day, calver.org was born. At the time my team maintained the Python infrastructure for eBay and PayPal, and we were stuck deciding whether we were really ready for a "major" 1.0 release. Semantic Versioning was the only game in town and "major" means "big", right?! Thankfully, a wiser colleague mentioned: Ubuntu and Twisted don't struggle with version number debates. They slap a date on it and keep shipping. In fact, their date-based versions were even better because you always knew where it stood, in terms of updatedness and support. The only problem is that no one really knew about it. Somehow, this problem solving versioning alternative, arguably as old as history itself, had gone nameless for millenia, conspiring to make me feel foolish in an office meeting. Never again! Ten years of adoption Fast forward 10 years, we've seen CalVer adopted by Apple, Nvidia, JetBrains, and countless others. (We have a timeline!) The site may have more inbound links than any other project of mine. Apple made the biggest jump, at WWDC 2025: iOS went from 18 to 26 macOS from 15 to 26 watchOS from 11 to 26 and visionOS from 2 to 26 All landing on one, consistent number like a car's model year. I still remember the texts from the Venn diagram fanbase of my friends who love Apple and reasonable versioning. No such texts from when NVIDIA announced calendar versions across the GPU Operator, RAPIDS, and its monthly NGC containers, but still very cool. Open source, too: Home Assistant, pip, CockroachDB, and yt-dlp all ship on dates, with plenty more on the users page. The conversation even reached the language core; PEP 2026 proposed versioning CPython as 3.YY, and it almost happened, too. And it's never too late, time marches on! Fixing the notation But I don't think I got every detail right from day 1. That's the main motivator for CalVer 26. It's high time to start righting a couple idiosyncratic token design choices, starting with some additions: Meaning Before 26.0 26.0 Full year YYYY YYYY Short year (6, 16) YY YY Zero-padded year (06, 16) 0Y 0Y Short month (1 ... 12) MM M Zero-padded month (01 ... 12) 0M 0M Short week (1 ... 52) WW W Zero-padded week (01 ... 52) 0W 0W Short day (1 ... 31) DD D Zero-padded day (01 ... 31) 0D 0D Seeing double First, the doubled letters. From the first version (16.6), MM and DD meant the unpadded month and day, which reads backwards to anyone who knows date formats (ISO 8601's YYYY-MM-DD, Java, moment.js, day.js), as some community members correctly pointed out. I was ready to flip them, until I checked what people actually use: most projects with a YY.MM.MICRO badge (conda, Twisted, Ansible's tooling) don't pad, and more than a dozen other version management tools (like bumpver and bump-my-version) implement the old meaning. So, it's too late to flip MM's meaning. Instead, 26.0 deprecates it and offers a more explicit and hopefully clearer option: M is the short month, 0M the padded one, and MM is a technically-retired synonym for M. In case you're wondering, the explicit 0M was me being overinspired by Ubuntu's approach, perhaps: 6.06 pads its month but not its year, and YY.0M says exactly that. To pad or not to pad I think it's worth a detour into why padding is even a thing anyways. It's become important now that new ecosystems have emerged that enforced SemVer formatting semantics, and I wanted clear guidance about on the spec site. SemVer forbids leading zeros outright, so Cargo rejects 26.04.0 and Go modules reject v26.04.0. Even Python's packaging spec normalizes leading zeros away, so you can tag 2026.08.19 if you want, but PyPI will still show 2026.8.19. NVIDIA's GPU Operator docs put it this way: "Zero padding is omitted for month to be still compatible with semantic versioning." CalVer was always intended to drop in where SemVer was used. So 26.0 recommends unpadded (YYYY.M.D) as a sane "pure" default for software libraries. But libraries are not the only objects of versioning schemes. The exception is a version that becomes a filename, an image tag, or an object-store key that gets listed lexically. There, padding keeps 26.10 sorted after 26.09, which is why Ubuntu, NixOS, and NVIDIA's own NGC containers pad. More evidence of teams designing their versions. We love to see it. Our FAQ has a longer discussion of the padding issue, as well. Optional segments There was never any rule against them, but 26.0 makes optional trailing segments more explicit with square brackets. Now, yt-dlp's scheme can finally be written down: YYYY.0M.0D[.MICRO]. For the CalVer badges I could find on GitHub, they all stay valid for now. I've got a note on the deprecated spellings and a new copy-paste badge section for new ones. What else is new? It's always a great time to add more citations to the site. A spec changelog; the spec now versions itself: 16.6, 19.7, 26.0. A FAQ: breaking changes, same-day releases, and padding. The users page, rebuilt by category, with past users of note (schemes change; that's fine) and tooling. Case studies: Apple and NVIDIA in, yt-dlp replacing youtube-dl. Much of the thinking behind these changes happened in the GitHub issue tracker over the years, and 19 or so issues close with this release. That's where ideas for CalVer should go, so by all means, open an issue, and we'll get it sorted! In due time, of course. In closing, I can't believe I still love belaboring these numbers so much. Thanks to all (but especially Mark, Glyph, Hugo, issue reporters, translators, and maintainers) for the discussion, and ultimately making the most timely versioning system a timeless classic. See also 2016 announcement Designing a version My Yap on Why CalVer beats Semver
Four sincere attempts at OKRs, four failures, and every time: "you didn't do it right." Maybe the framework doesn't fit you -- so how do you find one that does?
Multiple concurrent writers. Multiple processes. Same SQLite. No modifications.
Brilliant jerks, ZIRP-era managers, and how psychological safety lost the plot. Part 3 of my conversation with Dr. Cat Hicks.
Say you're deploying an AI assistant that processes online order returns. For it to work, it would need access to your store's purchase policy, item…