Devlog · NotNoita

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


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.

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.

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.

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.

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.

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.

18 – 27 August 2026

A hundred and thirty-two creatures

enemies: every biome has its own set now — 132 kinds each placed from its own Noita entity, 12 movement styles traced back to the components that justify them, 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.

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.

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.

7 September 2026

Tentacles, and a tower with nobody outside it

A heavy day — fifteen commits. Explosion streamers got ported properly: seventeen presets generated from source XML, 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.

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 of nine candidates was run and then applied — 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.

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 their reference says 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

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