11 – 12 July 2026
Weapons that manifest
The last feature in the log is a particle system built on a two-word thesis:
sub-emitters and curl noise. Not a trail renderer — a ribbon draws one continuous
strip behind the object, which is not what makes a projectile feel alive. Instead the
projectile emits thousands of independent particles that then live their own lives.
Two details carry the whole effect. Particles spawn per pixel travelled, not per
frame, so a fast bolt leaves a continuous line instead of dashes — and the
straight-versus-curved look is free, because it is just the projectile’s own ballistics
imprinted on the spawn positions. Then, while a particle is still fast, its own velocity
dominates and the line stays straight; as drag kills that velocity, the noise field
takes over and the trail visibly melts into drifting wisps.
The implementation is a struct-of-arrays CPU pool — sixteen parallel arrays, sixteen
thousand particle capacity — drawn as exactly two instanced meshes, one additive for
glow and one alpha-blended for smoke. Thousands of living particles, two draw calls. It
drops excess spawns rather than queueing them, so a burst storm degrades instead of
stuttering.
8 July 2026
Souls
The Dungeon Master’s resource had already been renamed three times — Threat, then
Essence, then Rage. The fourth was not a rename. Your form burns souls to stay
manifested; every slain monster feeds you its soul; at zero you fade away. The economy
and the fiction became the same object.
And one clause in the patch note is worth the whole feature:
Soul wisps: dying monsters release glowing souls that fly across
the battlefield into your meter, landing with a coin-like chime —
the bar grows when the soul arrives, not before.
0.9.13 — Soul Harvest
The same day brought twenty-five amplifiers, a weighted level-up draft with rarity, and
multiplayer rematch — when a co-op match ends, the party returns to the lobby together,
still connected, without re-entering the room code.
2 – 7 July 2026
The cliff
Four commits in six days after thirteen days of fifty. All three substantial ones are
infrastructure, and they read like someone cleaning up after a party they enjoyed.
Every setting in the game collapsed into one prefixed manifest, where the key is the
wire name and its prefix is its category — save defaults, console variables, UI defaults
and the options menu rows all generate from it. A review pass fixed real trust bugs,
including hosts now clamping guest stat loadouts and ignoring guest god-mode. And then
this:
Shortened every comment across the whole repo (C#, TS, CSS,
tools, tests — 224 files, ~−3,800 lines) to one-line
essentials while keeping load-bearing invariants, with
zero code changes — build, typecheck, and tests verified
clean.
That pass is why the codebase today contains exactly one TODO marker, and it is a
section header.
29 June – 1 July 2026 · days 11–13
The pipeline, then the effects binge
Day eleven is a small masterclass in sequencing: create plan for sprite sheet parser
tool → create sprite sheet parser tool → Hero Creator + animated directional
heroes. Plan, tool, content, in that order, in one day. The result is a browser
application that eats heavy multi-animation sheets and emits a manifest, a packed sheet,
a portrait and a roster entry, with the wire protocol following automatically.
Then 1 July, which is ten post-processing effects in a single day — bloom, colour
grading, dynamic muzzle-flash lights, contact shadows, vignette, CRT scanlines,
trauma-based screen shake, fog and falling ash, projectile trails, sun rays — each with
its own console variable, and a new Effects tab created to hold them all.
27 – 28 June 2026 · days 9–10, 64 commits
A second game mode in a single day
Maps and game modes became data, then Survival World was built almost entirely on 28
June — roughly twenty-five commits in a row all prefixed World:. Damageable cores,
siege beams, core line-of-sight, wall avoidance, monsters leashing to cores, heal zones,
in-world health and shield and XP bars, core destruction effects.
The same day the Dungeon Master got its two best toys: a gift-chest system where reward
chests hide arming traps that survivors spring by walking near them, and a wave-timeline
editor wired through one client-to-server path.
26 June 2026 · day 8, 78 commits — the peak
Fifty weapons, and a day lost to DPI
Expand weapon roster to 50 across 14 fire-mode families + 9
elements (add Water/Wind/Earth, Nova/Spiral/Bounce/Homing/
GroundZone/Smite modes, and burn/slow/pull status effects)
Then the busiest day in the repository turns into a fight with Windows display scaling,
in three acts: viewport units break at 150% zoom, then the debug panel jams partway
across the screen on high-DPI, then the actual fix — scale the entire web interface to a
constant fraction of the screen.
The underlying trap is genuinely nasty. The UI scales with CSS zoom, and Chromium does
not apply zoom to viewport units — so a raw 100vh resolves against the unzoomed
viewport and breaks on every screen that is not 1080p at 100%. Which is to say:
invisible on the development machine, broken everywhere else.
The response was a zero-dependency build guard that forbids viewport units across the
whole UI, wired into dev, build and lint, with exactly one allowlisted file — described
in the docs as “the ONE authorized place.”
23 – 25 June 2026 · days 5–7
The Dungeon Master appears — and the game starts playing itself
Added Dungeon Master role to multiplayer: a free pan/zoom
camera, a horde-summoning menu, and a live player minimap,
selectable in the lobby
The asymmetry arrives on day five. Two days later, so does the thing that makes
balancing it possible: a headless balance simulator where an AI hero plays the real
game, auto-drafting level-ups, time compressed. It drives the actual server sim — the
same spawner, boids, contact damage and gem merging — so what it measures is what the
game does.
Crucially it is seeded. Identical inputs reproduce exactly, so two builds can be A/B’d
on the same run. It has a bot-versus-bot mode where it plays the Dungeon Master too. The
balance commits that came straight out of it are specific in a way that guesswork never
is: cap player snowball + ramp late-game threat so a typical run ends ~7–11 min.
Two developers were working in parallel through all of this, and the log shows the
handoffs verbatim: “Good night the stick is passed to you.”
22 June 2026 · day 4
Squeezing the wire
Four optimisation commits in a row, each one a different axis. Data-driven authoritative
enemies, dropping about three hidden nodes per creature. Per-client snapshot culling
through a spatial grid instead of scanning every enemy. Then the one that defines the
protocol:
netcode: shrink combat snapshots — viewport-relative 0.5px
monster positions, drop velocity (interpolate)
A monster’s position is not a world coordinate. It is a 12-bit signed half-pixel offset
from the receiving player’s own hero, with the origin sent once per snapshot. Three
bytes, ±1024 pixels, and bounded so tightly that it physically cannot drift far enough
to become a ghost. Velocity is never transmitted at all — the client derives it. Total
cost of a moving monster on the wire: six bytes.
Same day: a lag-simulating transport so the netcode can be tested under conditions, and
a hundred hand-authored monsters across ten tiers.
21 June 2026 · day 3, 61 commits
The commit the whole codebase is shaped by
After a morning of Discord presence, localisation, accessibility and keybinds, the
largest structural change in the project’s history landed in one commit:
refactor: collapse netcode into server/client pillars over
pluggable transports; reorganize whole game by role
(server/client/shared/services) — single-player + multiplayer now
run one authoritative-sim + view pipeline (loopback for SP, ENet for
MP), with headless/dedicated-server support and a CI sync harness
Everything since obeys it. There are two pillars and nothing orchestrating them; they
talk only through a transport, and that is the whole contract. Single-player is not a
special case — it is a loopback with zero sockets running the same authoritative sim the
dedicated server runs.
Immediately before it, a bug that explains the motive: make EnemySpawner server-only so
clients can’t simulate ghost monsters the host never has. Immediately after it,
tests, a launcher menu, and a stress harness — the refactor was landed with the means to
prove it.
19 – 20 June 2026 · days 1–2
Two inits and a webview
The repository opens with init, then init again, then add webview, then Steam
support — and then one commit that is really a week of work: itch.io support, an in-game
console on the tilde key, a lobby, the documentation index, an icon and a splash screen.
Day two also removes Lit and adds hot reload, and ports UDP netcode — ENet transport,
prediction, delta snapshots — from a predecessor project. Networking exists on day two
because it did not have to be written on day two.
Lit comes back in the second-to-last commit of the repository, three
weeks later, as the only runtime dependency in the package file. A
full round trip.
Three war stories worth keeping
The FPS killer was not the swarm
With two thousand monsters on screen, turning the swarm on cost about one frame per second. The cliff was the weapons: every bullet was its own node doing a per-frame vector draw. Baking each weapon's procedural visual once into a shared atlas and making every bullet a sprite over an atlas region collapsed the whole swarm into roughly one draw call. Solo went from 5 to 40 FPS.
Render caps were tried first and reverted — capping projectile counts worked numerically but hid bullets, which is the wrong answer in a swarm game. The documentation records the rejection so nobody reintroduces it.
The black frames
On a busy host, the first render of an offscreen viewport can come back as an opaque black buffer — the transparent clear has not applied yet. So a bake captured early was entirely black on the host, while a pure client baking a moment later was fine. The fix is to retry across frames and only commit a frame that validates: transparent in the corner, opaque in a drawn cell.
The lesson that got written down next to it is the better artifact:
“FPS looks fine” does NOT prove correctness — black squares are just as cheap to draw as correct ones (that is how the blackout bug hid). Always eyeball, or assert, the actual pixels.
docs/netcode.md §5
The key-up that was stolen
The webview sits on top of the game window, and only one of them can own the operating system's keyboard focus. The game owns it by default. So when you held a movement key and clicked a level-up card with the mouse, the webview transiently stole focus, received the key-up the game was waiting for, and your hero walked off by itself.
The obvious fix — release all four movement keys when focus returns — is documented as wrong, with the reason: a key you are still physically holding only re-asserts for one key, because Windows key repeat only repeats the last one. That fix silently breaks diagonal movement. The real fix forwards the stolen key-up.
The thing that makes the log readable
Every version in this project has its own file, the version number is derived from the highest one rather than hand-edited, and there is a rule that a new day gets a new entry rather than piling onto yesterday's. Twenty-seven of them exist, each with a real title — the documentation explicitly forbids leaving one as “TBD.” There is even a rule, born of two people working in parallel, to check the last few commits first so one of them does not claim what the other already shipped.
The result is that the version table reads as a clean narrative — not because it was reconstructed afterwards, but because it was maintained as a hard rule while the sprint was happening.