Devlog · Wrecksong

Sixty-eight days in the sand

264 commits between 9 July and 14 September 2026. It started as a stripped-down multiplayer shell and ended as a falling-sand roguelite with 97 materials, 132 creatures and sixteen biomes. This is the order things actually happened in, and what each one cost.

9 Jul 2026264 commits · 68 days14 Sep

About the project


16 – 24 October 2026 · work in progress

The inventory, three times

In eight days the inventory and the anvil were rebuilt twice. Each version was filmed with the same showcase scene: the same hill at dusk, the same backpack and the same scripted pointer walking through the real panels. Watched in order, the three films show what changed and nothing else.

Before: two screens

As of 16 October the inventory was a small opaque box: the paper doll on the left, the backpack beside it, filter tabs underneath. The anvil was a separate full-screen panel. It had the carried weapons in a row along the top, the stats in a column on the left, the weapon art in the middle with its bays, and the card strip along the bottom. They were two screens that held the same items, and each covered the world while it was open.

During: one window

kit window: three pages over full-screen frost, backpack slides in and stays put, deal ticks as one rising sweep

On 19 October the two screens became the kit window, written up in the previous entry: three pages (inventory, cards, anvil) over one backpack, all behind frosted glass so the world stays in view. The anvil also lost a stat. Efficiency is gone; each round pays its own price.

After: one button and a bench

The window kept its layout and everything inside it changed. Tiles show rarity as a coloured bar instead of an outline, and parts are tinted in their rarity colour too. Empty slots are recessed sockets, including the empty equipped ones. Every filled slot on the equipment page now has a callout: a diagonal line of light out from the doll ending in the item’s name. Whatever you are holding pulses on every tile it would fit. The page tabs are etched into the frost, each with the key that opens it.

The input was the bigger change. Each page used to handle clicks in its own way, and the anvil placed a part on the next press rather than on release. Now there is one rule for all of them:

one shared left button for every page: click carries, drag drops on release, a drag onto nothing goes back

The anvil now works around a bench. Clicking a weapon no longer selects it. You drop it on the bench, an empty socket marked DROP A WEAPON HERE, and it zooms in from the pointer while its bays fade in. A mounted part can be dragged off onto a backpack tile and a better one dragged onto the bay. Mounted parts are drawn on the gun the way that weapon type draws them, so a scope or a stock has its own shape on each model. Drop a second weapon on the bench and the two swap. The x by the weapon’s name takes it off.

The tour script needed rewriting for the new anvil. The old version picked weapons by clicking them, which no longer does anything, so the new one drags every item.

20 October 2026 · work in progress

The kit window

The inventory, the card strips and the anvil used to be separate screens. Now they are three pages of one see-through window over a single backpack that slides in once and stays put while the pages change above it. The world keeps running behind the frost, so the creature walking in while you rebuild stays in view.

kit window: three pages over full-screen frost, backpack slides in and stays put, deal ticks as one rising sweep; tooltip: own node with frost and soft shadow, pop on open and every swap

Every page is dealt like a hand of cards, and the deal is the sound: one tick per tile that lands, rising across the page and on through the backpack. Rarer cards pitch their pick-up higher, so a legendary sounds like one.

The video is not a screen recording. A showcase scene stands the player on a hill at dusk and walks a scripted pointer through the real panels, so every hover, tooltip, drag and mount in it is the game’s own, sound included. It found its own bug on the first take: the window’s animations ran on the wall clock, and under the recorder, which renders slower than real time, every deal landed in a single frame.

13 – 14 September 2026

Reading a creature’s mind

The most recent stretch is interface work on a 960-unit canvas — the anvil, the inventory, the material drawer, all laid out in units two-thirds of a world pixel with text on its own finer grid — and then a debug overlay that turned out to be a bug detector.

you can read a creature’s mind now: cl_draw_ai puts its state, facing, footing and stuck window over its head […] out of the sim rather than the renderer so it cannot disagree with the brain; and two things it was built to find are already fixed — a facing of zero, which Math.Sign hands you for prey exactly level and which is neither side, and a dash with no cooldown, six ticks where it needs a hundred and twenty.

Building the window found the bugs before anyone looked through it. And the very last commit in the log is a small, satisfying piece of detective work: creatures were dying in their own puddles, and the culprit was not damage at all.

the route that was actually killing them was the STAIN, not the damage — sludge carries no hazard at all and its poison drain bled a fume-wisp out in twelve seconds standing in its own puddle

8 September 2026

Two lines, and nine candidates

A whole-repository pass with an unusual thesis:

a comment says its point in two lines — the 2,420 blocks that ran past four come down to two across the game, the tools, the shaders and the scripts, and the generated tables are shortened at their json and generator sources so a re-run keeps them short

The last clause is the part that makes it stick: shortening the output of a generator is temporary; shortening the generator is permanent.

Same day, an architecture review weighed nine candidates and applied them — six whole, three in part. One strike seam that every blow arrives through, one actor vocabulary, the table hash and the actor wire codec generated from the declarations they had been drifting away from.

7 September 2026

Tentacles, and a tower with nobody outside it

A heavy day — fifteen commits. Explosion streamers got done properly: seventeen presets, streamers attached to twenty-one more weapon families, and a fix for beads dashing instead of drawing a line, because trail gap now fills every frame rather than every emission.

The same day, a mode that is just a good idea:

the tower is the arena with nobody outside it: one sealed ledged room, the whole roster climbed weakest-first off the HP the kinds already carry, three kills a floor, and a death that costs the floor’s banked kills but never the floor

No new balance data — the ordering falls out of the health values the creatures already have. And the failure state gives back progress without giving back the climb.

31 August 2026

The fonts were never in the build

Every exported build had been wearing Godot’s fallback font. Not broken, not crashing — just quietly wrong, in a way you would only catch by comparing an export against a source run. The raw .ttf never entered the pack, while the UI reads it dynamically at runtime.

The fix is one line of export config. The interesting part is the response: a test class that pins every raw-read asset as both keep-imported and inside an include filter, and a build script that runs that one class before every export. The bug cannot come back, and neither can its siblings.

20 August 2026

The story closes

The narrative was not written in one sitting; it was tracked. A folder of fourteen markdown tickets with status and blocked-by fields, a map file recording a one-line resolution for each closed one, and a stated destination. Ticket 01 was “Fate of the old lore bible.” Its resolution: discarded wholesale and the file deleted.

The last ticket closed with the line “Last ticket — destination reached.” What came out of it: the Quiet Quarter, the Keeping, the Kept, and the sentence the whole thing hangs on — “keep this” arrives as “make this.” Plus three ambiguities that are deliberately never resolved, and a rule that no character will ever speak a line of dialogue.

18 – 27 August 2026

A hundred and thirty-two creatures

enemies: every biome has its own set now — 132 kinds, 12 movement styles, and a --zoo that stands the whole roster on one plain

A roster that size needs a way to look at all of it at once, which is what --zoo is. Then hitboxes, animations, and hibernation so the far ones stop thinking.

Somewhere in there is the single most specific bug in the repository. Animation rows kept vanishing. The cause was not in the game:

the zombie was never cropped — PowerShell’s -shl kept the PNG header a byte, so 113×300 read as 113×44 and 15 rows fell off; zombie 2→17, roster 961→1035

A shift operator in a build script silently truncated a third of the animation library, and the only symptom was that creatures had fewer poses than they were drawn with.

3 – 17 August 2026

It becomes a game

Two weeks of systems on top of a simulation that finally ran fast enough to carry them. Gun crafting across four platforms — rifle, blade, shield, drone. 140 cards. Damage types, stains, triggers, melee. A hand-written integer rigid-body solver with joints, terrain contacts and decomposition, because float polygon physics is the one thing that cannot survive lockstep. Trees, hanging lamps, stalactites. Nineteen more materials. A day/night sky. Biomes.

And all the audio, which is not audio: every sound is a synth patch rendered into memory at attach time. There are zero sound files in the repository. The studio page that authors them previews through the identical engine, so the preview is the bake.

1 – 3 August 2026

The pixel-grid reckoning

Three days, three bugs, all of them the same bug wearing different clothes: two rasterised grids disagreeing about where a pixel is when the camera sits between them. What makes this stretch worth reading is that none of them were eyeballed. Each got a counter.

First a picket fence of blended columns walking across the screen with the camera fraction — 7,338 of them, then 1,013, then zero on unlit frames. Then an orange hairline on every material edge that had a mountain behind it: 417 fringes, 3,096 pixels at 4K, now zero. Then the real one:

5.8875 device px per world px maximised at 4K, so 13% of world pixels drew 5 wide and the rest 6, redistributing every frame with the camera’s sub-pixel phase and walking every material silhouette a pixel against the parallax behind it. blended columns fall from a 2653-count oscillation to 76.

The fix was to stop rendering the world at 1080p and drop the whole canvas to 640 × 360. Two small tools exist purely to count these things — one counts blended columns, one diffs jitter between frames. They are the reason the numbers above are numbers.

29 July – 1 August 2026

Six passes at the frame budget

A big world reveals what is slow. The performance work ran in six documented passes, each one measured before and after rather than guessed at. The pattern was the same every time: instrument first, then delete.

Delete the off-screen light apron (+48% fps, +134% 1% low); instrument the lightmap’s seed/prop/blur split that found it

The instrumentation is in the commit message alongside the fix, because the split is what found the apron. Then the lightmap moved to the GPU behind a CPU-fallback toggle (1.39 → 0.18 ms per run), then terrain shading and the edge chamfer (0.73 → 0.14 ms). Then the fallback itself was deleted:

deleted the CPU render path so terrain, lighting and decals run only on the GPU (−996 lines), cut decal cost 1.26→0.69 ms, and set a 300 fps target

A separate one that pays back every single launch: reimport before every source run, gated on an mtime walk — and the insight is that pruning .git and the build folders during the walk takes the check from 16.4 seconds to 117 milliseconds.

25 July 2026

The world gets big

Fourteen commits in one day widened everything. Sparse per-chunk world storage — uniform chunks plus lazy blocks on 64-bit addressing. Lazy multi-focus baking: a bounded ring-scan around every player, so the world never fully bakes. Every coordinate type widened for scale.

The target it was built for: 10,240 × 6,144 cells, about twenty-one screens across and twenty-three deep, roughly 88% caves. A listen host holds two of those worlds — around 760 MB.

12 July 2026

The day it became data

Three days of sand, shaders and props, and then one commit long enough to be a patch note. Melting and freezing landed as a real temperature model — molten wax, glass, silver, gold and steel, with heat, melt point and freeze percentage as material properties, plus quench and radiant-ignition reactions. Weapons moved out of code and into weapons.json. A live Weapon Forge overlay appeared to edit them while the game ran. Characters became sprite sheets with optional descriptors.

Everything that mattered later is in that commit in embryo: the data tables, the live editor next to the running game, the sim reading the same file the tool writes. The pattern repeated for materials, enemies, cards, biomes, lore and sounds until fourteen browser pages were editing a live game.

9 July 2026

Clean, then init

The first two commits are called clean and init, twenty-two hours apart. The repository was carved out of a finished survivors-like — its own predecessor — and stripped back to the parts worth keeping: a Godot 4.7 C# project, a native UI, and the Quake session model where there is always a server and the local player is always a client, single-player being a loopback.

The scar is still visible. The C# namespace is SwarmNet, and the project file notes renaming it as “a deliberate later step.” Two months later it is still deliberate and still later.


What the shape of the log says

Across the whole history: 999,605 insertions and 398,265 deletions over 9,345 file changes. In the last thirty commits alone, 27,903 lines added against 28,122 removed — almost exactly balanced. That is not a project accreting features. That is a project being rewritten in place, repeatedly, by someone who deletes as readily as they add.

The other tell: one TODO comment exists in the entire codebase, and it describes a hack that was retired. Gaps do not live in the code as good intentions. They live in a todo file, there are three of them, and one of them says of its own subsystem that it is “two unrelated halves bolted together and neither is any good.”