> /etc/releases

Release Notes

Family entries summarize what shipped. Build tags beneath each family list the individual patches. Permalink anywhere via the #v0-XX or #v0-XX-YY anchor.

v0.88.4

This release doesn't add anything new to find in the dungeon

It rebuilds how the dungeon itself gets built — and along the way it caught a real bug worth knowing about.

Maps are painted, not pieced together. Every hand-built map used to carry its own scattered stack of tile layers, one set per room. Now each map has exactly one floor layer and one wall layer, painted directly with Godot’s own tools. Rooms are drawn as zones on top of that shared ground — labeled, colored regions with their own resizable extents, visible right on the canvas. A small editor add-on gives bulk controls over that view: hide every room label at once, or dial their opacity down, without touching the map underneath.

The home dungeon got rebuilt on the new foundation. Same rooms, same layout, same run — it just sits on cleaner ground now. A couple of visual seams got fixed along the way: doors sit flush in their walls instead of floating off to one side, and wall corners fill in cleanly instead of leaving a see-through gap.

The real find: some walls were half-blind. A handful of vertical wall segments only blocked movement and sight across half their width. Enemies on the other side could sometimes see and shoot through the edge of a wall that should have been solid. Fixed. Those walls block sight and movement across their full width now.

What didn’t ship. A dedicated tool for cutting wall openings got built, tried live, and cut the same week once Godot’s own right-click erase turned out to cover the job better; a door-snapping system was designed and dropped before a line of it was written, for the same reason. The wall-cutting logic underneath stays, though — it’s the seed for next release’s dynamic dungeon generation. Hallway generation, saving and loading maps from the tool, and dedicated zone-shape tools didn’t make it in either; they’re queued for the next release, pulled in once real map-building proves they’re needed.

See pm/roadmap/0.88.4-map-builder/ for the full feature tree.

v0.88.3

The first full dungeon run plays start to finish

No boss yet — that’s next — but every other piece of the loop is real and working.

Deploy to a place, not a difficulty. The breach console used to make you pick a tier and a run length before every trip. That’s gone. Open the console now and pick a destination from a named list instead — Diablo-style waypoints, grouped by tab. Right now there’s one: “/home.” Select it, hit Deploy, and you’re standing in the dungeon.

The run. You land in a three-room dungeon. Somewhere in it sits an exposed Node — shoot it down and it tears open a portal. Step through and the boss door ahead, which has been locked (and visibly dimmed) since you arrived, wakes up and swings open. Walk into the boss’s room — empty for now — and ride the portal there to the treasure room, then take the return portal straight back to the hub. (The treasure itself arrives with the boss in an upcoming release — the room’s ready for it.) Every warp in that chain, forward and back, runs through the same portal mechanic, so it behaves identically wherever you meet one.

Rooms introduce themselves. Walk into an authored space for the first time and it announces itself: a title card with its name and a line of flavor text. That’s automatic now for every place that’s been given one, not a handful of hand-wired exceptions.

Fixes caught in live playtesting, in order of “how did that ship”:

  • Enemies in multi-room dungeons were invisible — they took hits and died correctly, you just couldn’t see them do it. Fixed.
  • A hand-built dungeon room’s identity and rules silently didn’t apply — the on-screen room name was blank. Fixed.
  • Powerup pickups (surge, fortify, overclock) stopped doing anything after an unrelated cleanup pass deleted code they were secretly still using. Restored.
  • A boss door could miss its own “all clear” check if you killed the last Node too fast. Fixed.

Under the hood. Every placeholder sprite still being drawn by code at boot — CORE-1, the breach pad, the signal pillar, the treasure chest, doors, the hub’s NPCs — is now committed art instead. The generator scripts that used to build them at runtime are deleted; what you see in the editor is what ships.

See pm/roadmap/0.88.3-dungeon-entry-through-boss/ for the full feature tree.

v0.88.2

Quarantine is a real place now

Every wall, floor tile, sprite, and light in the hub is hand-placed art, not a stand-in. CORE-1 walks on real terminal-tile floors instead of a flat color fill. The three hub contacts — the Programmer, the Archivist, the Scheduler — have proper sprites and stand where they always have, but you can now see them instead of squinting at a placeholder shape. The six protocol pedestals, the breach pad, the signal cache pillar, and the wall sconces are all drawn, not drafted.

The hub got bigger and darker.

Quarantine scaled up 8x with the same tile grid, giving the space room to breathe. Lighting changed with it: the baseline is pure black now, with no ambient floor glow anywhere, so your torch and the sconces are what push the dark back. Each room can carry its own light mood; the hub reads warm and lived-in, and a dungeon will read colder once one exists.

Name tags overhead now work on anything, not just NPCs — a pillar, a light, whatever needs a label can carry one. They stay upright and readable no matter how the thing under them is rotated or turned.

Fixes that came out of playtesting the new hub.

A regression during the rebuild briefly killed every hotbar key for the whole session after walking into the hub — fixed, and pinned so it can’t silently come back. Vertical wall corners didn’t join and let floor bleed through the seam; walls are re-cut so corners meet cleanly and no light leaks past them where two walls meet.

Under the hood

This release is the foundation the hub art sits on: every world piece — rooms, tiles, entities, lights — is now a placeable object you drop and configure in the Godot editor, not something generated or built by a script at boot. No converters, no generators, no build step standing between what you place and what ships. That’s what let CORE-1, the NPCs, and every hub prop become real art in one pass instead of a slow trickle. The dungeon itself — rooms, enemies, and a boss you can actually fight — is next.

See pm/roadmap/0.88.2-visual-dungeon-builder/ for the full feature tree.

v0.88.1

Ships alongside v0.88

The Thread follows you through doors now.

A navmesh wiring bug was causing hired Threads to wedge at room thresholds or orbit the wrong doorway. Off-center door placements left navigation links pointing at the wall rather than the gap, so the pathfinder had no valid route through. Fixed: nav-link endpoints now ride the door’s wall normal so the path lands on the right side of every threshold. Your mercenary keeps up.

Minimap and rendering tightened

The minimap was re-scanning every placed tile on every frame to decide which color to paint it — a full-map sweep, continuous. Tiles are now latched at zone-entry instead, and the frame-time regression that snuck in with v0.88’s safe rooms is gone. Separately: the menu CanvasLayer now hides when menus are closed (it was drawing even at rest), and light-cull range was pulled in to match the play area. The game runs smooth again.

Safe zones hold

Enemies standing in a safe zone hold their fire — ranged attackers check the rules at their own position when they’re about to shoot, and if combat is off there they stand down. You’ll see a visible posture change when an enemy crosses the boundary and parks. Environmental hazards that deal damage skip protected targets the same way. This is foundation for content that mixes combat and safe rooms; current dungeons don’t route enemies into safe zones, so you’ll mostly feel the absence of fire rather than the feature itself. The plumbing is there for when the dungeon builder needs it.

Under the hood

v0.88.1 is the other half of the spatial model work that started in v0.88. The pull half — reading what rules apply at a position — was done. This milestone fixed the push half: “a source does something to an entity.”

Before this ship, damage, healing, and status effects all reached their targets through different paths. Hazards called one interface; the healing fountain called another; abilities and throwables and contact damage called a third (or a fourth, or a sixth). No common contract meant every new producer had to know what kind of entity it was hitting and reach in accordingly.

There is now one path. A producer stamps a typed effect message and sends it. The target decides whether to act on it — or not. Every entity that can receive effects accepts or declines by capability, then handles the payload in its own code. No producer reaches in. Many entry points became one.

Zone reactions (door-seal, combat-lock) used to live in the dungeon paint code, keyed off where the zone came from. They’ve moved onto the zones themselves via a shared paint pass — so a zone reacts the same way whether the dungeon painted it or a test fixture did. That’s the property the Visual Dungeon Builder (v0.88.2) is built on.

A static scanner gate now runs on every push: it flags any code that reaches into an entity to mutate it the old way, so the unified path can’t quietly grow exceptions back.

See pm/roadmap/0.88.1-cursors-and-effects/ for the full feature tree.

v0.88

The dungeon has places to stop now

You’ll find them at roughly one per run — a room drawn from the SAFE pool rather than the combat queue. Three variants, weighted: the Neutral Zone (quiet, no enemies, edit your loadout); the Sanctuary; the Den. The last two are the new ones.

Sanctuary

A church-shaped room. In the center is a fountain. Stand in it to tick-heal until you step out or the wound closes — whichever comes first. Once you leave, the fountain is spent for that dungeon; it doesn’t refill. A figure named Vigil stands near the door and greets you when you approach. No shop, no menu — just words and the fountain.

The Den

A hookah-den with its own music track. The Programmer is here — the same one from the Quarantine req terminal, placed in this room. Walk up to start a transaction. The existing shop opens. No bespoke inventory, no separate system; it’s the same Programmer instantiated mid-dungeon so you can resupply without dying first.

Both rooms keep combat gated and loadout editing on while you’re inside. Neither room is a boss gate — you walk through when you’re ready.

The minimap now reads the room

Revealed floor tiles take a color from the zone they sit in. Safe rooms tile differently from combat rooms; the Den tiles differently from the Sanctuary. As you explore, the minimap fills in behind your vision and marks what you’ve stepped into. “Where was that safe room?” is answerable from the map now.

Doors fixed for two-player runs

Threads could soft-lock doors by crossing at the same time as the player. One body would complete the transit and the door would close on the other mid-crossing. Fixed: doors track occupancy on both sides and stay open until the doorway clears.

Under the hood

v0.88 was mostly architectural. The spatial model was rebuilt from the ground up: where the dungeon used to track which room you were in through a pair of central per-entity trackers (one for the player, one for enemies) and a room_type_changed broadcast, space is now a tree of self-tracking regions — each zone monitors its own membership and carries its own rules. The old trackers are gone. The camera, combat gates, music, spawn logic, minimap, run log, first-entry title cards, and the Quarantine’s predicate system all migrated onto the new substrate before the deletion ran. Doors became pure motion sensors (open on entry, close when clear) and lost the per-crossing FSM that was causing the soft-lock. A scanner scenario now locks the anti-pattern dead so the old central-tracking architecture can’t re-grow. The safe rooms are the proof: building the Sanctuary and the Den was straightforward once the substrate landed — each is a named region with its own ruleset, drawn from a weighted pool, authored like any other room.

Prop placement migration (adapting the world builder’s prop atoms to the new substrate) deferred to v0.88.2.

See pm/roadmap/0.88-in-dungeon-safe-rooms/ for the full feature tree.

v0.87.3

Two bugs from the v0.87.2 playtest, fixed

Spectre’s recall Dash works again. Spectre’s Dash is a two-press move: dash out, leaving an anchor at your starting position, then press Dash a second time to teleport back. That second press was getting killed by the cooldown before it could fire the recall — so the return trip never happened. The recall now fires on that second press even while the forward dash is still spent.

Aim-mode range follows you. When you line up a throwable that has an aim mode — a frag grenade, a void spike — the range circle and the aim reticle were anchored to wherever you stood when you triggered it, so walking away threw your aim read off. Now the range circle, the reticle, and the throw clamp all track your current position as you move.

The hired Thread’s pathfinding is unchanged this release. Getting it to reliably follow you through a doorway turned out to be a door problem, not a routing one — the door can close on the Thread while it’s still mid-crossing behind you — so the proper fix lands with the door work in v0.88.

See pm/roadmap/zz_archive/0.87.3-pathing-and-dash-fixes/ for the full feature tree.

v0.87.2

The Thread follows you into the next room now

Before this patch, the Thread (CORE-N) you hired from the Scheduler would walk straight into the wall separating your current room from the one you just stepped into. It was doing exactly what you told it, technically — moving toward you — but it had no understanding of the space between you. It would grind against the dividing wall and stay there until you came back. Same story for enemies chasing you: a rusher that lost line of sight would just press into the geometry.

v0.87.2 is the fix. All chasers — the Thread, the rusher trio (probe, tracer, dropper), and the drone — now route through the world instead of walking through it. They find their way around walls, through corridors, and into the next room. A hired Thread will follow you when you cross a doorway. Enemies that had you cornered can now route out of that corner rather than grinding against the wall on a target they can’t reach.

One core instead of three

As part of the wiring, the chase logic that was duplicated across several enemy types got consolidated into a single behavior. The rusher trio and drone now share one parameterized chase behavior (chase to engagement range, then hand off to attack — or chase to contact, depending on type), and the Thread’s follow drive runs the same routing math. No gameplay change; it just means future changes to chasing touch one place instead of three.

Where this leaves the door

A lone Thread still can’t open a closed door by itself — that’s a known issue, and the proper fix is coming in v0.88 with broader door work. What changed here is routing: the Thread now correctly routes to the doorway and waits at the threshold when the door is closed, rather than pressing uselessly against the wall. When you open the door and cross, it follows. The soft-lock where a single Thread could jam a door shut (a multi-body collision bug, not a logic bug) is also deferred to v0.88 — it didn’t surface in real-play one-Thread+one-player transit, which was verified to cross correctly.

See pm/roadmap/0.87.2-robust-pathfinding/ for the full feature tree.

v0.87.1

Ships alongside v0.87

The Scheduler gets a proper store.

Hiring a Thread used to be a one-line confirm — “Spin up a Thread — 1 cycle” and you were done. That worked, but it gave The Scheduler nothing to preside over. v0.87.1 replaces it with DISPATCH: a store board that reads like the labor pool it’s supposed to be. CORE-N sits at the top, buyable, available, the same reliable numbered instance it’s always been. Below it: a roster of names that are out on other jobs — MUTEX, SEGFAULT, KERNEL PANIC, GREP, SUDO, each with a blurb and the note ALLOCATED ELSEWHERE. The Scheduler doesn’t say “coming soon.” It says its pool is mostly spoken for, and what’s left is yours to look over. The hire still costs one cycle; the Thread still spawns and fights and is joined when you return. The surface is new; everything under it shipped in v0.87.

Named entities get their name above them.

The Thread already wore a floating name badge — CORE-N, amber-bordered, floating above the ally so you could tell it from yourself at a glance. That same treatment now belongs to everyone in the staging room with an identity: The Programmer, The Archivist, The Scheduler, and each protocol pillar (SPECTRE, SEEKER, SWARM, SENTINEL, FLUX) each wear a clean overhead badge in their own color. The labels painted on the floor under them are gone. It’s a small thing, but the staging room reads sharper for it — every named entity announces itself the same way, in the air above where it stands.

See pm/roadmap/0.87.1-mercenary-store/ for the full feature tree.

v0.87

You are no longer alone down there

In the staging room — the cold marshalling space you pass through before the breach — a new figure stands. The Scheduler. It was an allocator before the collapse, the quiet arithmetic that decided which process ran next. Its queue is nearly empty now. But it still has spare capacity, and you are still filing requests.

For one cycle it will spin up a Thread: a worker built from the CORE-1 image, given just enough of your shape to fight beside you for one sortie. It follows. It shoots the nearest thing it can see. It can be downed, and when it downs it is gone — the pool does not keep what exited. No revive. No shared rewards. One Thread at a time. What it gives you is simple: something between you and everything else that wants you dead.

The Thread displays as CORE-N (a random N assigned at spawn) with a thin amber ally-ring and a name badge so you can tell it apart from yourself at a glance. When you return to the Quarantine the run ends and the Thread is joined — folded back into the pool, its slot freed. The Scheduler notes the outcome without sentiment.

Breach Portal — a beat before the boss

Clearing the last node used to advance the dungeon immediately. Now it spawns a Breach Portal where that node fell. Walk into it when you’re ready. The boss is behind it, but the choice to cross is yours. For the Thread it comes through free.

Timer retired — run size, not countdown

The run timer is gone. The countdown, the timeout death, the Null-arrival overlay — all deleted. What stays is run-size choice: Short, Medium, Long. The choice still shapes how large the dungeon is, how many nodes and safe rooms you get. It just no longer implies a clock.

Aiming and charging feel sharper

Two effects used to slow the whole world — throwable arc preview dropped time to a quarter-speed crawl, and Spectre’s charge at 90%+ halved it. Both are gone. Arc preview now shows at full speed with a brighter animated trail; Spectre’s charge ring carries the timing read without freezing enemies mid-flight. The game keeps moving. The feedback moves with it.

Stat-allocation tooltips

Hovering the Primary and Secondary pip selectors in the Character menu now shows what those allocations actually mean (20% and 10% of your stat pool, respectively). The protocol hover is restructured to show your Signal Strength, total pool size, and how the 70/20/10 split plays out across your chosen protocol and stat picks. The numbers were always there in the math; now they’re in the UI.

Under the hood

Boss arenas now place every active player into authored spawn slots rather than fanning positions radially off a single marker. For a solo run this is invisible; for a run with a Thread it means both of you land in intentional spots. A documented audit of remaining single-player assumptions now lives in the architecture specs, with a fix-recipe for each site that wasn’t worth changing this milestone. The SDLC toolchain gained agent-worktree isolation, auto-gate hooks, and a shellcheck pass over every dev script.

See pm/roadmap/0.87-multiplayer-ready/ for the full feature tree.

v0.86.3

# v0.86c — Import-Artifact Hygiene

No player-facing changes. A maintenance patch that pays down a recurring repo wart and stops it recurring.

Godot identifies every script by a stable uid://…, and scene/resource files cache references to it. A .uid regeneration that didn’t resave the files referencing it had left a handful of data definitions pointing at stale ids — harmless at runtime (the engine falls back to the file path) but noisy on a cold build, and the kind of drift that quietly comes back.

This realigns the drifted references, commits the full set of .uid files so ids stay identical across machines and CI, and — the part that matters — adds a guard that fails the build if a reference ever drifts again or a script lands without its .uid. The policy was always written down; now it’s enforced instead of remembered.

v0.86.2

# v0.86b — Gameplay Input Routing

Input now works the same no matter where the cursor sits.

v0.84 surfaced a specific break: right-clicking to aim fired only when the cursor was over a HUD element, not over the bare play field. That was one symptom of a broader structural gap — every input reader lived wherever it was first written, and none of them agreed on what “the play field is in focus” meant. Most reads worked by coincidence. The ones that didn’t were hard to find and easy to re-introduce.

v0.86b is the fix that holds. Every gameplay verb — fire, move, aim, gadget placement, the radial wheel, menu toggles, minimap, hotbar belt-cycle, and every hotbar slot regardless of whether it’s mouse- or key-bound — now has exactly one place it gets read. That place is inside the game viewport, layered correctly. A single context gate decides whether it’s listening: if a menu is open, field input stops. That gate is enforced in one place and applies to everything.

Interactive HUD (slot buttons, menus, the wheel) consumes its own clicks. Passive HUD (the minimap overlay, cue indicators) lets clicks through. The two categories no longer mix, and anything that was accidentally blocking clicks it should have ignored is corrected.

The part that matters for the future

A regression here used to be silent — a new reader gets added somewhere that looks fine, and six months later a playtester reports that some ability only fires over the wrong part of the screen. That failure mode is now a CI gate. A scanner runs on every commit and fails if any new gameplay-action read appears outside the one owner. The fix doesn’t just hold for today’s code; it holds for code that hasn’t been written yet.

No new content in this release. This is a reliability foundation for the input system — the kind of work that makes the next six months of feature builds cheaper to verify and harder to break.

v0.86.1

# v0.86a — Test Framework Foundation

One gameplay change and a complete rebuild of how we verify the game.

Vector perceives you regardless of light

The Tier-1 boss arena is sealed. Vector is always hunting. Its awareness profile now reflects that — it no longer requires lighting to detect you, matching the way the fight actually works (locked room, no escape, attacks through walls). You can no longer hide from it in the dark.

Under the hood — new test framework

The old verification suite ran ~357 scenarios serially, each booting a fresh headless process. A full regression took about 18 minutes. That’s long enough that “run before you ship” stopped being automatic.

It’s been replaced with a two-tier framework. The first tier is a GUT unit layer — 217 tests that don’t boot the game at all, targeting the contractual math and logic seams that can be verified without a running dungeon. They run in seconds, sliced per system. The second tier is 98 integration scenarios, each in its own isolated process, running in parallel. Full validation now takes about 2.5 minutes. Per-system subsets during development take seconds.

About 100 low-value tests were dropped in the process — scenarios that tested authored balance values, UI blend modes, editor plugins, or render outcomes that a headless process can’t actually see. They weren’t catching real bugs; they were slowing every run and obscuring the ones that were. The ~256 remaining scenarios were classified, restructured into the two tiers, and the old tests/scenarios/ directory was deleted.

The rules that keep the framework lean are encoded in the tier READMEs and auto-loaded for agents: unit tests don’t boot, integration tests stay few and genuine, new behavior gets a test in its system’s directory rather than a new standalone file. That last rule is the one that prevents re-bloat.

v0.86

Audio stops being a flat wall of sound and starts telling you where things are

v0.82 made the dungeon spatial — rooms got bigger, weapons got louder when fired nearby. v0.86 finishes the job. Distance now shapes every sound in the dungeon: a kill across the room sounds like it happened across the room, a hazard you haven’t reached yet buzzes under its own alarm loop, and aiming your scope at a distant enemy makes it louder — because your ear leans where the camera leans.

Positional emitters — continuous sound as a spatial cue

Loops are a first-class thing now. The AmbientEmitter node can attach to any entity — props, hazards, doors — and sustain a positioned audio stream that swells as you approach and muffle through walls. Armed hazards buzz proximity warnings. Doors carry ambient loops plus distinct open and close SFX. The architecture is shared: one emitter node, the same through-wall attenuation math, drop it onto anything.

In-world SFX go positional

Enemy deaths, teleport bursts, and other world events were still emitting at flat global volume after v0.82’s weapon pass. They’re migrated. A kill across a large room is quieter than the one at your feet; a Seeker bursting into position sounds like it came from somewhere.

Aim-directional listening

Scoping in already moves the camera toward your aim anchor. It turns out that also moves the audio listener — the game’s existing camera behavior gives you directional hearing for free, and the through-wall occlusion check now leans the same direction so aiming toward a sound can also un-occlude it. Scoped aim at a distant source: it gets louder and clearer. Release the scope: it pulls back.

Per-cue attenuation

Every AudioKey can now carry its own distance profile: how far the sound reaches, how steeply it falls off, how much wall dampens it. Explosions carry across a room; footsteps don’t. A narrow registry maps the cues where it matters most; everything else falls back to the global default.

Mix channels + a dB mixer in Settings

Six buses — Master, Music, SFX, Ambient, UI, Voice — replace the old flat volume sliders. Settings → Audio shows six faders; each bus has a mute floor. Music sits under SFX by default (mastered at −8 dB below the player slider) so ambient SFX aren’t buried by the score. The mix numbers ship as playtest values — they’ll be re-tuned by ear once final ambient assets replace the current placeholders.

See pm/roadmap/0.86-spatial-audio-vocabulary/ for the full feature tree.

v0.85.5

# v0.85a — Visibility + Lighting Rework

The dungeon is dark now, and you see by light.

This milestone rebuilt how you see the world. The old system conflated two jobs — “is this lit?” and “can I see it?” — into one tile mask, which produced a recurring hard-edged shadow wedge and a shimmer along light cones. v0.85a separates the two: lighting is a GPU outcome, visibility is a line-of-sight gate, and the fog remembers where you’ve been.

Lighting is visibility (the marquee)

You see a thing only if it is lit and in your line of sight. Light and its shadows moved to GPU Light2D + LightOccluder2D, so shadows are smooth by construction — the tile-quantized shadow wedge and the cone shimmer are gone at the root. The three-state fog reads cleanly: black where you’ve never been, a faded memory of rooms you’ve left, fully revealed where you stand. There’s one light model — your personal ring matches your held light and defers to your gadgets (the flashlight narrows to a cone, the lantern pools a warm glow), and a procedural floor gives that light something to reveal. Doors are pure occluders with a dark core and a bright outline; enemies, motes, and drops render above the dark so they always pop. An in-game lighting tuner (F8) ships with the tuned look baked in.

Void holes feel like gravity

Void spikes now actually damage enemies — they had been silently affecting only the player (a collision-layer mistake), so they dealt zero enemy damage and pulled you into your own hole. Fixed: the well affects both you and enemies, pulls about twice as hard, and captures with a smooth inward swirl instead of yanking targets straight to center where light enemies used to vibrate. You can no longer just zip through one.

Hotbar and consumables

Throwables and consumables now show their stack count (×1, ×2 … capped at ×99+) on the hotbar and in the abilities menu, grey out when you’re empty so you stop dry-firing into aim mode with nothing to throw, and the count updates live as you throw or restock. Character, Inventory, and Abilities all open mid-combat now, not just in safe rooms.

Signal cache ceremony

The level-up ceremony is one fixed-size window that the beats play out inside — announce, the encrypted decode roll, the reward, the summary — instead of four boxes that resized and jumped. The decode “slot machine” scramble is restored, and long reward descriptions wrap instead of running off the panel.

Under the hood

Persistence was rebuilt on a Persistable / SaveService backbone — wallets, the bestiary, memory fragments, and the gear/charm stash each own their save domain — and several god-objects were decomposed along the way. Signature items and source-granted abilities became native activatables (one unified activation framework instead of hardcoded grants), the room behavior registry was retired, and a headless scenario manifest now guards the test surface so coverage can’t silently drift.

v0.85.4

# v0.85c — Godot-Native UI Authority

The UI looks the same. Most of what shipped is invisible by design — and that’s the point.

v0.85 built the shared design vocabulary. v0.85b made the authoring consistent and enforceable. v0.85c hands the remaining authority to the engine itself: styling declared once in a project Theme, navigation wired into the widget library, fixed motion authored as editor tracks, and every player-facing string routed through a translation pipeline. The pattern is the same across all four: take something hand-rolled per-node in code and give it to the native home that scales.

Keyboard and gamepad navigation (the visible win)

You can now navigate every menu without touching the mouse. Arrow keys and Tab move focus between buttons, list items, and options; the controller D-pad and stick do the same thing. A themed focus ring shows exactly where you are. Opening a nested dialog restores focus to where you were when you close it.

This was pure gap — there was no focus authoring in the game at all before this milestone. Desktop and controller players had menus that had to be moused. That’s fixed, and it’s fixed at the library level: the atom widgets and modal shells own the contract once, so every surface inherits it rather than needing a separate wiring pass.

Screen effects — new Visual settings (the other visible win)

Four new toggles live under Settings → Accessibility → Screen Effects: Threat Tint, Death Desaturate, Low-HP Vignette, and Combat Flashes. The last three default on at the same intensities as before. Threat Tint ships off by default — the effect exists and you can enable it, but it needs a tuning pass before it earns its on-by-default spot; rather than hold the milestone for that, the toggle is there and the intent is honest.

Behind the toggles: the existing flash and vignette treatments were consolidated onto a single composited shader overlay instead of two different mechanisms competing for the same screen space. Multiple treatments that fire at once — a hit at low HP while threat is high — now sum and clamp into a readable frame rather than stacking independent rects toward overload.

What you won’t see

The theming migration (styling declared in a project Theme rather than hand-set per control), the AnimationPlayer motion tracks (modal open/close, toast, spinner, the decode reveal), the localization pipeline (every string now routes through tr(); a translator would have an extraction target to work from), and the native decode text effect (the same per-character scramble-to-clear, driven by the text system instead of a frame timer) are all at visual parity. You won’t notice the difference — that’s how this pass was supposed to go.

Hotbar fix

Wiring the project-default Theme caused the primary hotbar slots to render tall and rectangular instead of square — the belt-nudge arrow buttons inherited a padded menu style they shouldn’t have worn. Fixed: the arrows carry a zero-margin compact treatment and the slots assert a square contract regardless of sibling height.

What’s next

The font-size rollout deferred a consumer sweep — the HUD, tooltips, stash rows, and dialogue surfaces carry residual per-label size overrides that will roll into the next milestone that touches those surfaces. And the threat tint still needs its tuning pass before it defaults on.

See pm/roadmap/0.85c-godot-native-ui-authority/ for the full feature tree.

v0.85.3

# v0.85b — UI Implementation-Quality Hardening

The UI got consistent, and the consistency got enforced so it can’t drift back.

0.85b was a quality pass on the design system v0.85 established. Category and option selectors that read as chunky buttons are now folder tabs (Archive, the Requisition Terminal, Run Confirm) or a compact recessed segmented control (the subcategory filters, the deploy run-length / tier / player pickers, settings toggles) — one look per job. The Requisition Terminal, Archive, and Run Confirm now share a single window size, so they read as one system. Inventory rows are identical on every tab: a small icon in a rarity-colored surround, the name, and a count.

Under the surface, every UI screen is now authored in the Godot editor instead of built in code — the tooltips, the HUD belt and key indicators, the Archive panel, the stash rows, and the deployable interaction menu were all converted, so their layout is editable by hand. Every surface declares which design-system revision it conforms to, the whole atom + base-scene library is cataloged in one place alongside an “authoring a new surface” playbook, and a build-time scanner now flags any new screen that re-rolls a widget, builds a layout in code, or skips the system — so the consistency holds going forward rather than relying on review to catch drift.

Also in this pass: the loadout redesign (the protocol compass with its grants inside, a compact stats grid, the recessed accordion), and a run of fixes surfaced along the way — including rarity sort finally ordering the whole list rarest-first across categories instead of only within each tier.

v0.85.3

The game reads as one thing now

v0.85 was three phases of work and a build-model overhaul, all pointing at the same outcome: every screen you open feels like it belongs to the same product. Phase 1 built the shared vocabulary — one Theme, one cursor, one icon set, one tooltip widget — and proved it against the four front-end menus. Phase 2 rebuilt the HUD atom by atom. Phase 3 redesigned every heavy in-game surface from scratch, then shipped a structural change to how progression works. By the close, there are no more handmade controls that look almost-but-not-quite like the adjacent one.

What the design system means for you

The OS cursor is gone. There’s a custom terminal-aesthetic cursor now, with distinct shapes for interact, grab, resize, and target states. Every close button across every menu is the same icon, the same size, the same hover. Hovering an HUD element shows a tooltip through one consistent widget — HP, shields, signals, echoes, keys, active effects, equipped gear, consumables. Nothing on the HUD is silent on hover. Settings reads as one coherent panel for the first time.

The front-end menus (Phase 1)

The Start screen is a clean centered console: title, a glowing DEPLOY button, and a captioned icon rail for Settings, About, and Exit. The Pause menu sits as a modal over the dimmed game with a three-stat snapshot and a corner X to dismiss. Settings grew a sixth tab — Camera — and the controls finally line up at the same scale and padding. The How-to-Play tutorial was removed entirely. The About screen was rebuilt from scratch with a scrollable, categorized attributions manifest.

The HUD (Phase 2)

The top bar is a five-region host: meta-state cluster on the left (level, cycles, echoes, keys — each with its canonical icon), the mode’s live objective in the center, active effects as a grid of category-tinted atoms on the right. The bottom bar’s action slots are built on the same atom the Abilities screen uses — readiness looks the same everywhere. The boss HP bar belongs to the same bar family as your own. The old radial wheel is gone.

Phase 3 — the heavy surfaces

Loadout. The fullscreen 4,180-line loadout is replaced by two independent edge-anchored panels: Character (C) slides in from the left with a protocol compass, gear slots, a stat accordion, and derived stats; Inventory (I) slides in from the right with the stash browser. Both panels can be open at once. Neither pauses the game clock or triggers slow-motion.

The Archive. The Bestiary is now The Archive — a centered modal with an Archivist-green accent, two tabs (enemy tier cards with live silhouettes, and the System Memory fragment grid), and a reading sub-modal that opens the full entry.

Requisition Terminal. The Store is now the Requisition Terminal — a centered modal with a Programmer-purple accent, per-item icon wells, a wallet readout, and an ACQUIRE button that routes through the buy flow.

Initialize Breach. The deploy-confirm screen is redesigned as a five-region authored host renamed “Initialize Breach” — mode tabs, a deployment snapshot, and a gated confirm that won’t let you deploy with nothing loaded.

Claim ceremony. Picking up a signal now opens a Reveal Chamber — a multi-beat sequence with stage headers, reel pip progress, and a decode well where each character locks in left to right, shifting from white to the item’s rarity color before the name and tag crossfade in.

Title cards. The tier-intro splash is now a slot-based cinematic system: eyebrow, icon, title, rule, and subtitle slots inside letterbox film bars that slide in and sweep out. The Null Arrives is a data card now, not a bespoke draw blob.

Run summary and death. The post-run screen has a hero-tile stat row, grouped Combat and Salvage clusters, and the signature-item roster. The death branch gets a crimson “NULL ADVANCES” framing. A single adaptive button advances the flow.

Abilities menu, minimap, gadget widgets, dialogue bubbles. The abilities screen composes the same slot atoms as the HUD. The minimap’s chromeless transparent overlay stays exactly as it was (the one surface where “keep as-is” was the right call). The gadget widgets — lantern and flashlight — got a normalized window shell with a clickable power light, a titlebar that never clips, and 4-corner resize. Approach and idle dialogue bubbles now carry a speaker name badge, a quadrant-aware pointer tail, and smart placement that picks the corner of the screen farthest from the viewport edge.

Build and progression

The old belt-slot unlock system is gone. You could place consumables and activatables freely from v0.84 onward, but the level-gated container-capacity mechanism (the one that used to lock hotbar slots until you hit a threshold) is now fully retired. No slot is ever locked. Scarcity moved off the container and onto the thing itself.

Deployables are the first consumer of the new model. They’re permanent level-gated purchases at the Requisition Terminal — you need to hit the level, then spend the cycles to own one. At any given tier the cost is high enough that you’ll realistically afford one, and the one you pick defines your build the way a mount purchase does: deliberate, run-shaping, yours between sessions. The level-up ceremony no longer announces belt slot unlocks; it announces when a deployable tier becomes requisitionable.

Under the hood

Every centered dialog and edge-anchored panel in the game now shares one underlying chrome — scrim, bracket-corner frame, header band, content slot. Surfaces stopped re-rolling their own frame. ESC reliably opens pause regardless of which menus are open or what sequence you navigate in. The in-game Settings screen is interactive again and ESC from it returns to Pause correctly. Character and Inventory menus no longer slow the game clock when they open. The mode controller now exports a live objective element and a deploy-params element that HUD slots pull by duck-type — adding a new mode no longer requires touching the HUD.

What’s next

v0.85a — Visibility and Lighting Rework. The personal-ring occlusion work that was scoped during the v0.85 playtest deferred out because it surfaced deeper visibility-system questions. Those get the full treatment next.

See pm/roadmap/zz_archive/0.85-general-ui-polish/ for the full feature tree.

v0.84

Everything you can activate now works the same way

v0.83 shipped gadgets as the third parallel system for something the game already did twice — equip a thing, press a key, watch the cooldown. Three separate code paths, three separate belt rows, three sets of slot UI that looked almost but not quite identical. v0.84 collapses them. Consumables, deployables, gadgets, and every granted ability now run on one framework with one HUD. The refactor is invisible if the old game felt right; the benefit shows up everywhere you previously noticed a seam.

One slot type, everywhere

The HUD now has three slot containers built from a single primitive: a persistent ten-slot Belt you can scroll between, three bespoke corner Special spots for things you reach for reflexively, and three summoned radial Wheels for the rest. Every slot works the same way — it holds whatever you put there, shows readiness in one ring (a cooldown sweep for timed abilities; a filled-vs-empty count for stacked consumables), and fires on its bound key or a click. Readiness is readiness; the slot doesn’t need to know what category it’s holding.

The three v0.83 category belts and three category wheels are gone. What replaces them is shorter to describe and faster to navigate.

Shoot, dash, ADS, cycle-element — player abilities now

These verbs used to be hardcoded parts of the player character. They’re granted abilities now, the same as any other. Drop them in a slot, bind a key or click to fire. The dash cooldown ring stays on the player character where it’s always been — but what drives it, and what drives every other ability, comes from the unified framework. Swap your protocol and your dash slot automatically becomes the right dash for that protocol; no reassignment.

Shoot is the only ability with a default assignment (bottom-left spot, left mouse button), so a fresh character can fire. Everything else starts in the Abilities screen waiting for you to place it.

The Abilities screen

A new screen (K) shows everything you’ve unlocked in one filterable catalog — consumables, deployables, gadgets, abilities, signatures. Drag an item to any slot or click to assign. The mapping saves and reloads across sessions. When you unlock something new, the game offers a best-effort default placement once; deliberately remove it from its slot afterward and it stays where you put it, not where the game put it first.

Deployables are permanent unlocks now

Before: find a Repair Drone pickup, deploy it, use up your stock. Now: unlock the Repair Drone, deploy whenever the cooldown clears. The drone is yours between runs; you’re just waiting out the timer between uses.

Rarity rolls fresh at placement. The one Repair Drone you own might drop as common one use and rare the next. One live instance at a time — redeploying replaces the previous one. The global limit on field-count across all deployables was removed late in the milestone; per-deployable cooldowns do that job without the artificial ceiling.

All v0.83 deployables unlock automatically at the start of v0.84 so the shift is immediately playable.

Safe-zone gating

Offensive abilities grey out in safe rooms and don’t fire — weapons, deployables, throwables. Movement, healing, gadgets, ADS, and dash stay active. The slot shows why it’s greyed. The gate flips live when you cross the doorway.

Auto-trigger

Right-click any slot to turn on auto-trigger for whatever’s in it. With auto-trigger on, a healing consumable fires automatically when your HP drops to 20% or below. The slot shows a slow gold inner-edge light so you can see which slots are watching. Right-click again to turn it off.

Channel Element floater

Cycling through your equipped elements now shows the name of the element you’ve landed on floating above the character for a second. You no longer need to glance at the HUD to confirm which element is active.

Right-click ADS fixed

Aim-down-sights was firing correctly when the cursor was over a HUD element, but not when it was over the game world — the opposite of what you’d want. The input read moved to the right layer. ADS now works from anywhere on the play field.

What’s next

v0.85 — General UI Polish. One coordinated sweep across every screen: a shared control vocabulary, a real cursor, a standard icon set, and the settings menu rebuilt to match the rest of the game.

See pm/roadmap/0.84-activatables-unification/ for the full feature tree.

v0.83.7

The dungeon stops being something you move through and becomes something you hide in

v0.83 is the milestone where lighting stops being atmosphere and starts being gameplay. Enemies respond to light now. Stepping into a sconce’s glow means they see you. Dimming your lantern and slipping into shadow means they don’t. Throwing a flare into a side corridor pulls aggro away from the path you actually want. Scoping into a dark room reveals what’s there without telling the room you looked. Every room now has a lighting question on top of the combat question — and they interact.

It shipped in waves: the headline 0.83.0 features turned the lights on as a mechanic, a 0.83.1 patch closed out the minimap regression and a cluster of perception residuals, and then a longer tail of perception polishing, bug-fixing, and a deep code-filesystem audit reshaped the project’s internals.

The headline shift — enemies notice light now

The v0.82 dungeon looked like a stealth game. It just wasn’t one. Enemies were geometrically aware — cone-shaped perception, LOS-checked, spatially bounded — but completely indifferent to whether you were lit or in shadow. That was the right v0.82 ship (build the substrate honestly; don’t bolt on mechanics it can’t support). v0.83 turns it on.

Enemy perception now runs four filters: geometric overlap, facing cone, line of sight, and a lighting check. All four must pass. Walk through a bright sconce in front of an alert enemy and you’re seen from further away. Hug the dark corridor behind the same enemy and you’re invisible until you step into the light.

The threat curve is continuous, not a threshold. Dim light means partial threat — the enemy might drift toward you, might investigate. Full bright means full aggro from maximum range. A FLOOD-level lantern combined with standing in a sconce gives you 1.2× threat — the overbright tactical penalty. There’s no on/off line to abuse; there’s a gradient that rewards positioning.

The floor matters too. Shadow doesn’t collapse your threat to zero unless you’re running a stealth ability — you still leak 15% of your built-up threat into the dark. Natural decay brings the number down over time. Enemies you’ve already aggroed don’t forget because you stepped sideways out of the light.

Gadgets — the lantern and the flashlight

CORE-1 now carries gadgets. Not settings. Not passive gear. On-screen widgets with keybinds, visual state, and tactical presence.

The lantern is your default light source. It has a five-notch brightness slider — OFF, DIM, NORMAL, BRIGHT, FLOOD — and a key (F) to open the widget while you play. Turning it off makes you dark by default. That’s the baseline shift: the dungeon assumes you have light only if you carry it and keep it on. FLOOD gives you wider coverage at the cost of making you louder to every enemy in line of sight.

The flashlight is a directional cone. Two axes: brightness and beam width. Aimed at your crosshair. The narrow setting gives you reach in one direction; the wide setting sweeps a broader arc at shorter range. Forward light only — the lantern handles your player-tile exposure; the flashlight handles what’s in front of you. Both can be on at once.

Each gadget has an in-game wheel (G) for slot management and a Loadout panel for equipping. The same UI pattern runs for items and deployables — gadgets slot in alongside them, same visual language.

Wall sconces — the environmental anchor

Rooms now come with sconces. Dense placement in Quarantine means the hub stays well-lit regardless of your lantern state. Sparser, more deliberate placement in combat rooms means every sconce is a tactical piece: a lit spot that works for you (visibility) or against you (exposure), a chokepoint you might want to destroy (10 HP, anything damages them) or a corner you want to stay near because it lets you see the door approach.

Sconces are props — a new composable framework. A sconce is data describing a light behavior, a destruction behavior, and a sprite swap on break. Future props (torches, ambient hum fixtures, terminals) author the same way without new code.

Flares — two tools that got clarified

The original flare concept was one item doing two things. That didn’t work. They split:

Decoy flare — no light. Pure threat-attractor. Throw it into a corridor and enemies pull toward its position because they see it generating broadcast threat. The decoy’s own ambient exposure determines how loud it broadcasts — a decoy in a bright room is louder than one in shadow.

Light flare — no threat. Pure illumination. Warm amber, flickering, 18 seconds of steady light before a 2-second fade. Lands, glows, reveals tiles. Enemies don’t perceive it and don’t react to it. Use it to light a dark room before clearing it, or to write a path to REMEMBERED so you can navigate back out.

Both cost 1 at the requisition terminal, stack to 5.

Scope reveal — looking without being seen

Hold ADS and a small circle of light appears at your aim cursor — not from the player, but at the target point. The circle is LOS-gated from you: aim into a room through a doorway and the circle lights what’s there. Aim into a wall and nothing happens.

Things the circle illuminates are written to REMEMBERED on the minimap — you can scope a room, release, and still see the rough shape of what was there. Enemies illuminated by the scope do not perceive you back. It’s reconnaissance, not exposure. Per-protocol tuning: Spectre gets a wider, brighter circle; CORE-1 gets a tighter tactical ring; binoculars protocols sit between.

The scope engagement itself also got faster this milestone — the camera used to slide into position over a noticeable fraction of a second. Now it snaps, or near-snaps, with per-protocol tuning for the feel you want. Spectre can set instant. Others can tune a very-short smooth.

Minimap grows up

The minimap rebuilt on the visibility substrate in 0.83.1 (a regression repair after the initial minimap rewrite). What shipped after that isn’t just a tile-state mirror — it’s a navigational instrument.

Doors render at their real world positions with lock-state coloring. The player marker is a directional triangle (not a dot). Enemy markers appear only when you have line of sight, then disappear — a remembered enemy position is stale, so it’s not drawn. Item drops, memory fragments, deployable allies, hazards, and static room features (cores, chests, pylons, boss room) all appear once you’ve seen them and stay until they’re gone. A settings toggle hides hazards for players who prefer the surprise; default is on.

The world-extent black background is gone. The minimap draws only what you’ve explored; unexplored is just absent.

Entity glows — the dungeon reads better at a glance

Enemies, motes, orbs, chests, and deployables now emit a subtle glow through the fog. In a dark room you can see the silhouette of something alive before you’ve lit it — without knowing exactly what it is. Doors glow too. The world tells you where the interesting things are without turning the lights on for you.

Doors — cleaner presentation

Door animations migrated from code-driven tweens to declarative sequences authored in the editor. Open, close, locked-shake — each state is a named animation, tunable without touching code. Doors also lost their glass transparency: closed doors render opaque with a per-variant tint. Less visual noise during combat; the tint still reads what type of door you’re looking at.

The code-filesystem audit — the codebase matches what it describes

The biggest non-visible work of the milestone was a three-phase audit of the project’s filesystem.

v0.82 through early v0.83 added a lot: props, gadgets, visibility systems, perception systems, deployment refactors. Each piece landed where it landed. No one stopped to ask whether the directory shape still matched the systems those directories were supposed to contain.

Phase 1 cleared known vestigial findings: parse errors, dead debug instrumentation gates, a group with no live consumers, a retired test factory with stale API references.

Phase 2 reorganized the filesystem so the directory tree matches the system boundaries. Activation states, camera, audio, VFX, effects, input, cooldown, notification, run log, bestiary, memory fragments, progression, protocol, hazards — each migrated to a systems/<name>/ module with its own README declaring public API, internals, and dependencies. Entity subdirectories decomposed: players, enemies, throwables, pickups, effects, world fixtures, deployables, projectiles each got their own folder instead of sitting flat in a pile of 50+ files.

Phase 3 walked the most common developer tasks through the reorganized tree — “add a deployable,” “change a gadget behavior,” “find the boot path,” “localize a bug from a symptom” — and patched anything that still took too many hops. Nine class_name declarations converted to preload (the ones that didn’t need to be visible to the editor). A marker sweep cut stale version-stamped breadcrumbs from 1,571 to below the 160-target. Dead code was deleted; comments that described past states were rewritten or removed.

The single biggest concrete win: GameConfig, a vestigial settings file inherited from early development, got deleted entirely. It still had fields, getter wrappers, and an authoritative-looking presence. But every field had been superseded by proper subsystems years earlier. Dead weight masquerading as configuration. It’s gone.

The codebase shape now matches the systems it describes. Walking the tree cold, you can find where things live without grep.

Bug fixes — the lighting regression and the run-return observer

Two bugs shipped fixed.

Lit-tint leaks — a regression introduced by v0.83’s own dynamic lighting. When the ambient light-tint from the dungeon’s fog shader hit cycle motes, pickup sparkles, and floating damage numbers, it washed their authored colors toward whatever the room’s ambient tint was. The fix applies an unlit canvas material to all informational world-canvas entities: they paint their authored color regardless of ambient lighting. Twenty-two entities received the fix across a full audit pass.

Signal cache pillar — returning from a dungeon that ended in GAME_OVER back to Quarantine left the signal cache pillar unable to trigger level-ups on the return visit. The sequence that swapped mode back skipped the startup observers the signal cache pillar depended on. Fixed.

What’s next

v0.84 — Activatables Unification. Gadgets, consumables, and deployables now run on three parallel implementations of the same concept. v0.84 collapses them into one framework, rebuilds the HUD bar to match, and redesigns the loadout to give you one coherent place to configure everything that activates.

See pm/roadmap/zz_archive/0.83-dynamic-lighting/ for the full feature tree.

v0.82

The dungeon stops being a sequence of rectangles

It becomes a place.

v0.82 is the milestone where the rooms got bigger, the camera got smarter, the lights actually mean something, and the dungeon stopped pretending to be a corridor of identical viewport-sized cells. It shipped in waves: a tentpole 0.82.0 release with the headline features, then a four-patch tail (0.82.5 → 0.82.7) that closed out the visual root-state, hardened the enemy / hazard / deployable perception model into a single primitive, and unified the world so every entity lives at the same level of reality.

The headline shifts

Bigger rooms with real geography

Three new room templates ship — a double-wide corridor, an atrium with bridges crossing open holes, and a dense pylon maze that forces you to navigate around cover. Pylons block line of sight. Debris clutters paths. Medium-length runs now contain one big room; longer runs contain two. These aren’t just more space — they read as somewhere specific, and you fight differently in each. Underneath, every room declares itself in integer tiles (TILE_SIZE = 16) and the dungeon places them at real world-tile coordinates; doors auto-place along shared edges via mutual opt-in.

The camera follows you now

The room-locked camera is gone. The camera trails behind you, shifts toward wherever you’re aiming, and widens automatically when you walk into a larger room. Zooming is a real system now: hold the scope binding and the view tightens toward your crosshair. Every protocol gets at least binoculars; Spectre slides into full sniper depth over a second — camera untethering until it’s pointed entirely where you’re aiming. All tuning is .tres-authored — CameraConfig for the global feel, per-protocol ScopeConfig for sight personality.

Real fog of war

Walking into a room used to flip the whole room on at once. Now it doesn’t. Your torch lights a radius; walls cast shadows via recursive shadowcasting; what you haven’t aimed toward stays dark. Tiles you’ve visited dim to a faint remember-state — you know the shape of where you’ve been, but not what’s there now. Zooming the camera out does not reveal anything. The character is what sees; the camera is just framing. This is the primitive v0.83 Dynamic Lighting plugs into — sconces, flashlights, flares, threat-light coupling.

Per-tile minimap

The old minimap drew each room as an equal-sized box. The new one stamps tiles as you reveal them — rooms appear at their real proportions, at their real positions. Your dot is your actual world position, not “center of current room.” Walking through a doorway fills in only the slice of the next room your torch reaches. Walls render. Room-type tinting persists.

Enemies that aren’t just waiting

Enemies in adjacent rooms no longer sit frozen until you step through the door. Close enough to an enemy and they wake up: wander, drift, patrol, scan — whatever that type does while idle. Get into their perception cone and they engage; lose them and they search. A probe rushes forward-facing and won’t notice you sneaking behind it. A lens has a narrow optical scan and is trivial to flank, terrible to walk into directly. Performance stays clean in large rooms because enemies out of reach go dormant. The whole thing is a five-state machine — DORMANT / IDLE / WAKING / ENGAGED / SEARCHING — and every enemy authors its own IDLE + Search behavior palettes.

Quarantine looks right now

The Quarantine zone was using leftover lighting code from before the visibility rebuild. It rendered with a washed-out, overlit sheen that got worse as you visited more of it. The same fog substrate now runs everywhere — dungeon and hub alike — and Quarantine boots with everything already revealed (dim-remembered, full-bright under your torch). The sheen is gone.

Weapons are audible again

An audio listener flag that was never set correctly meant player weapon fire was not spatialized — at some zoom levels, shots were effectively silent or quieter than intended. Fixed.

Patch wave — polish + visual root state (v0.82.5)

The 0.82.0 ship surfaced a stack of playtest issues. v0.82.5 closed them out.

Fog-of-war visual contract

The fog-of-war shader had a filter_linear setting that was bleeding light across tile edges and producing a banded look at the lit-set boundary. Switching to filter_nearest killed the bleed. The world canvas modulate now sits at Color(1,1,1,1) (white) at root — a non-obvious requirement the rendering chain depends on. Document captured at docs/lessons/FOG_OF_WAR_VISUAL_CONTRACT.md for the next time fog-of-war regresses; the load-bearing facts are the white modulate and removing groups before queue_free on the fog overlay so the swap doesn’t race.

Visibility gates the minimap

The minimap was revealing tiles the fog hadn’t lit yet — flashlight ahead of the player’s actual sight. Re-gated on the visibility set; the dot now syncs with what you can see.

Scope-release zoom restores your default

Holding scope, releasing, and re-engaging used to leave you at the scope’s zoom-in level instead of returning to your protocol default. Release now snaps back to the player’s authored zoom — including for the Spectre’s deep-sight return.

Player-torch energy tuned

The player torch was sitting at energy = 1.5 and washing the floor immediately under the player into pure white. Dropped to 0.4 — closer to how a torch actually behaves at the player’s feet. The lit radius is unchanged; the burnt-out hotspot is gone.

Stale dungeon geometry on Quarantine return

Dying in a dungeon and returning to Quarantine used to render the hub with the previous dungeon’s wall fragments still visible around it. Two compounding causes: VisionConfig was overwriting the canvas modulate back to black on every mode-swap; build-time geometry (walls, interior structures, boundary lines) wasn’t being tracked by the broker so cleanup() never saw it. Both fixed.

AwarenessZone re-emits when line-of-sight clears

Drones and lens turrets near doorways could end up “dumb” — they registered the player while standing in the door pocket (wall in raycast path), logged wall_blocked=true engaging=false, and never re-evaluated. The geometric body_entered edge had already fired so the player walking the rest of the way through the door didn’t generate a new event. Awareness now polls per-frame for LOS-cleared edges and re-emits perceived — the enemy promotes to ENGAGED the moment the wall stops blocking.

SFX no longer drops out during doorway transit

Adjacent-room music / SFX-pool reparenting was evacuating playing voices to the main viewport mid-tween, which silenced them. Reparenting now keeps voices in their pool; mid-play instances are stopped before reparent rather than left dangling.

Sanctuary-room hole teleport

Hole-fall hazards in Sanctuary rooms were teleporting the player into adjacent combat rooms because the destination was hard-coded to the active broker. Destination resolution now walks the broker’s known anchors; Sanctuary holes stay Sanctuary-local.

Wall rendering through the light overlay

Walls were rendering visible through the light overlay in remembered-but-unlit tiles — they should have been masked. Light-overlay opt-in now correctly excludes wall geometry from the filtered set.

Vector boss combat fixes

Engagement at the entrance

The T1 boss was reliably failing to engage when the player crossed into the arena, with the player walking up close and Vector just patrolling. The FIX 9 force-engage LOS override that hides bosses from the perception system was scoped too broadly — it was bypassing the ROOM-membership gate. Now scoped correctly; Vector engages on arena entry every time.

SFX-pool crash on the boss fight

The Vector fight was hitting a “previously freed object” error in the SFX pool reparent path: when the per-zone DungeonWorld was freed-and-replaced, playing 2D voices in the pool were skipped (not reparented) because their is_playing() returned true, leaving them parented to a dying container that was about to cascade-free them. The next play_sfx call hit a dead slot. The reparent now stops mid-play voices and reparents them; the pool survives the swap.

Boss arena dissolve regression

After Vector died, the cover walls + hole tiles in the arena were supposed to dissolve to reveal the boss chest area. The dissolve was reading from a retired broker collection (the _entities field went away during the spatial-deref refactor). Rewritten to walk the world groups directly + filter by the broker’s world rect — the dissolve plays cleanly.

Hazards + deployables get the same perception primitive

Across v0.82.6 and v0.82.7, the same perception primitive that enemies got in v0.82.0 spread to deployables and hazards. This is invisible to the player but is what makes the whole milestone hang together as one consistent model.

Deployables compose AwarenessZone

The sniper turret + repair drone — the v0.82-roster deployables — now perceive the world via the same AwarenessZone that enemies use. The legacy “walk every group every frame and gate by distance” fallback retired. The composer that builds awareness layers from AwarenessDefinition was extracted as AwarenessComposer so deployables / hazards / enemies all share one builder. Visible knock-on: deployable behavior is uniformly reactive — the sniper turret only fires when the awareness zone genuinely reports an in-LOS, in-cone target; the repair drone hands off targets the same way.

Hazards too

Void holes, leech fields, gravity fields, wormholes, void spikes — every hazard with an “affects entities in radius” component now composes one or more awareness layers. The void hole authors two: a wide one for pull, a narrower one for damage-over-time, with target_tier_id on each behavior so the right candidate set flows in. Hazards now own an ActivityStateMachine too — when no player is near the hazard’s activity zone, the hazard goes DORMANT and stops ticking. Far-from-player rooms full of holes don’t burn cycles.

Hole-fall rewrite

The hole-fall hazard’s old state machine — FALLING_PENDING + body_entered + body_exited callbacks woven across the resource — went away. Replaced with a per-frame _poll_first_player_inside(detector_area) check. Roughly eighty lines of state-machine code deleted; the behavior is identical end-user; the bug surface is much smaller.

Hazard-tick rule clarification (developer-facing)

The contract is now: every entity that asks “do I need to wake up + apply behaviors based on player proximity?” composes the ActivityState system. The doctrine doc at docs/specs/systems/activation-states.md was rewritten as a cross-entity adoption contract, with a Don’t in CLAUDE.md against rolling bespoke “near player → tick” patterns. Deployables opt out (always-on by design); enemies, hazards, and anything new opt in.

Late polish

Eight boot warnings cleared

Three INCOMPATIBLE_TERNARY warnings on string vs StringName resolved by explicit String() casts. Four UNUSED_PARAMETER warnings on dungeon-spawn helpers underscore-prefixed (the parameters dropped meaning during the Kind-A entity refactor). One UNUSED_VARIABLE removed. Boot is now silent.

Pedestal refresh on protocol unlock

Unlocking a protocol family via the level-up claim overlay mid-dungeon now refreshes the corresponding Quarantine pedestal as soon as you return — no extra dungeon round-trip required. Quarantine subscribes to ProtocolManager.protocol_unlocked and runs its existing idempotent roster refresh.

Approach-engagement decoupling

The approach-trigger zone semantics decoupled cleanly from enemy engagement — what brings a player close enough to engage and what enemies actually engage with are now distinct concepts, which matters as you tune room scale.

Under the hood

The structural work this milestone did is the surface the next several milestones build on. Three big changes:

Rooms own nothing

In v0.81 a Room was a Node2D with walls, floors, hazards, powerups, and enemies parented under it. In v0.82, all of that lives at the world level (under DungeonWorld) and a Room is a RefCounted data class holding room_id, a world_rect, room state, and door topology — no entity registry. “What room is at this point?” routes through DungeonSpace.point_to_room(world_pos) — a single spatial-query contract. The room broker registry retired (lookup is point-in-rect, not name-keyed). PlayerRoomTracker and EnemyRoomTracker (per-physics-frame sticky-membership autoloads) are the sole writers of room_id on their respective entities. The dungeon-world is a per-zone dynamic Node2D swapped out on mode advance.

One perception primitive

AwarenessZone is the geometric Area2D + LOS filter + cone facing for all entities that perceive other entities. AwarenessDefinition.tres authors radius / shape / cone half-angle / LOS-blocker mask / memory TTL. AwarenessComposer builds the runtime zones from the definition. The exact same primitive is consumed by enemies, deployables, and hazards. Edge events emit perceived / unperceived; per-frame polling re-emits on LOS-cleared edges so consumers can’t get stuck because of timing.

One activation primitive

ActivityStateMachine is the five-state DORMANT/IDLE/WAKING/ENGAGED/SEARCHING machine. ActivityZone is the inner-radius + outer-radius push-Area2D pair that drives the DORMANT/IDLE transition. ActivityPolicy.tres authors the radii + always_active opt-out. Per-behavior activity_state_mask filters which states tick which behaviors. The host’s _process loop is identical regardless of whether the entity is an enemy, hazard, or anything else — the mask is the per-behavior gate, the state machine is the per-entity gate.

The shipping consequence: no entity in the game uses PROCESS_MODE_DISABLED for “freeze adjacent rooms.” Two scenario scanners assert its absence.

Known issues + deferred

These are open at milestone close and will be picked up in subsequent milestones:

  • Lighting additive stacking (medium) — persistent ambient lights using BLEND_MODE_ADD can stack toward white in shared regions. v0.82 catalogued the per-source classification + correct defaults (docs/specs/systems/lighting-authoring.md); the remaining audit is scheduled.
  • Minimap walls not drawn (medium) — some wall segments don’t draw on the minimap depending on stamp ordering. Tracked.
  • VisionField shadowcaster over budget (medium) — the recursive shadowcaster occasionally over-budgets on the largest rooms with the densest pylon counts. Mitigation in the v0.83 sweep.
  • Big-room placement budget tight in short runs (low) — short runs don’t always fit a big room cleanly given the placement budget. Authoring-side tuning, not a code regression.
  • Worktree dev-loop sidecar (low) — first-time worktree setup needs the full Godot import cache + sidecars copied through; tracked for the next dev-experience pass.

The fog substrate, the minimap, the entity activation system, and the room broker are stable. v0.83 Dynamic Lighting will layer on top of them — sconces, flashlights, flares, threat-light coupling, stealth abilities — without rebuilding any of them. That’s what this milestone bought.

See pm/roadmap/zz_archive/0.82-bigger-rooms/ for the full feature tree, including the v0.82.5 / v0.82.6 / v0.82.7 in-flight features that closed out the milestone.

v0.81

The protocol select menu is gone

The protocols are not.

Copy Code — diegetic protocol swap

Six pedestals line the south wall of the Quarantine now, one per protocol family, in canonical order: CORE-1, Seeker, Sentinel, Spectre, Swarm, Flux. Every protocol that’s unlocked and not currently running stands on its pedestal as a character. The one you’re piloting is the one that’s missing — out on the field with you.

Walk up to a pedestal, get the prompt: “Copy Code? ✓ ✗”. Confirm, and the swap unfolds in about a second. The target protocol’s code streams from the pedestal toward you. You pop into their shape. Your old self materializes on their pedestal behind you, waiting. The whole thing is animated — a visible code stream flowing pedestal-to-player, a transform burst, then the reverse stream dropping the previous protocol back in place.

The round-trip works exactly as you’d expect. Become Seeker, walk back to CORE-1’s pedestal, confirm again — you’re back to baseline. No menus, no separate selection screen. The roster is the Quarantine.

First launch lands you as CORE-1. No picker. CORE-1’s pedestal starts empty because CORE-1 is you; the first time you swap, the baseline character appears there.

Swarm and Flux don’t have characters yet — their pedestals stand with empty labeled slots. Visible future content. They’ll have characters when they ship.

Idle ambient dialogue

Protocols standing on pedestals aren’t quiet. Sparse, random bubbles pop up from time to time — “I wish I had something to stab.” (Seeker), “Holding position.” (Sentinel), “You can’t see me.” (Spectre), and from CORE-1, when you’re piloting anything else: “I wish I knew where my sibling was.”

The bubbles suppress automatically while any conversation or interaction is active — they won’t interrupt a swap prompt or a chat with the Programmer. Once the interaction closes, the idle timers reset and the characters go quiet for a bit before speaking again.

These are placeholder lines. The state-keyed, run-history-aware versions — reactions to last run, Null taunts, fragment counts — are deferred to v1.6.0 Living Quarantine. What ships now is the substrate: a composable emitter system built so the content depth can layer in later without touching the underlying wiring.

Under the hood

Both pedestal characters and your piloted character now share one source of truth for how a protocol looks — body gradient, core glow, glyph tuning. Previously the visual was authored in two places and could drift; now it can’t. The shared renderer is what makes the pedestal characters look exactly like you do when you run them.

See pm/roadmap/zz_archive/0.81-protocols-as-npcs/ for the full feature tree.

v0.80

Everything got sharper

Visual overhaul — drawn, not sprited

v0.80 pushed the procedural art to its ceiling before any sprite pipeline enters the question. Every entity in the game received a depth pass: shadows, edge bands, body gradients, and identity treatments that make each thing feel like it exists on a surface rather than painted flat on it.

The enemy roster got a full visual archetype framework. Drones have a triangle head that tracks their aim direction. Turrets read as hull-and-fin silhouettes rather than boxes. Heavy units carry horizontal armor-plate seams. Eye-type enemies got a sclera-and-iris layering that makes them legible even when small. Caster variants now have a faint ambient aura. Every entry in the Archive’s Bestiary renders the real in-game archetype instead of a generic circle.

The player character gained an inner glow, a move bob, and the protocol glyph painted on the body face — so CORE-1 reads as the specific protocol it is running. Vector gained vertex shading that makes the triangle read as a floating pyramid with a lit face and a still shadow underneath.

On the NPC side, the Programmer now has a lit terminal tablet docked to its body. The Archivist pulses a rim glow when the player is carrying unprocessed fragments — a visible signal that it has something to do before you have walked over.

Walls and doors got top-edge highlights and bottom-edge shadows. Pickups (powerups, item drops, cycle motes, memory fragment orbs) each have their own procedural 2.5D treatment: outer glow, radial gradient core, idle breathe animations, and drop shadows.

Floor islands are real now

The old way to make a floating platform was to carve holes in the room collision rectangle. It worked well enough, until it did not — walking onto a platform with one pixel of your collision circle over the edge still triggered a fall, because the cutout matched the visual, not the player’s actual body.

Floors are now first-class room entities, the same class of thing as walls. Each floor has real collision that answers the question “is the player grounded here?” The hole hazard asks that question on every overlap tick; as long as you are on a floor, nothing happens. Walk off the edge and the next tick catches it.

The Vector boss arena expanded from three platforms to five. Two full-width edge floors now frame the central fall pit on both sides, so the arena reads correctly — safe ground at the edges, void in the middle.

Deployable audit

The gatling turret is gone. It did not work and the fix was not worth the slot on the roster. The three remaining deployables — drone, repulsor, and sniper — went through a full audit pass.

Enemy fire now actually damages deployables. Previously, projectiles could not reach a deployable even when aimed directly at it; a new collision layer routes enemy shots through the correct damage path. The drone and repulsor are AoE passthrough by design (no hitbox of their own — they affect the area, not a body), but the sniper turret absorbs fire and can be destroyed.

The decoy flare, which is supposed to draw enemy fire away from the player, was not taking any damage from the projectiles it attracted. The routing check that handles projectile impacts was looking in the wrong place for a self-grouped entity. Fixed.

Each deployable has a threat value now, so enemies with threat-aware targeting treat a dropped sniper as a meaningful priority instead of ignoring it.

Visually, each deployable has its own identity treatment. The drone traces a lazy infinity loop above its shadow, rotor blur suggesting lift. The sniper’s crosshair is now state-driven — scan, lock, tighten, fire — each state shifting the aim-line before the shot lands. The repulsor emits orbital flecks and draws visible tethers to any slowed enemy inside its boundary.

Deployable belt

Deployables used to work differently from consumables — you owned a pool of types and all of them were available mid-run. That mismatch is gone. Deployables now use the same belt pattern as consumables: four slots, filled at the loadout screen before the run, picked from during the run.

The loadout screen’s deployable row became four drag-target slots. The Items/Deployables tab split — you browse consumables on one tab, deployables on the other, drag either to their respective belt slots. The filter bar chips now wrap to a second row when there are enough of them, so the panel no longer clips at the right edge.

Radial wheels

The slide-up deployable bag is retired. Both the consumable belt and the deployable belt now have center-screen radial wheels:

  • T opens the deployable wheel. Four wedges, one per belt slot, slot 0 at twelve o’clock and the rest clockwise. Empty slots are visible placeholders, not hidden — position stays stable so muscle memory works.
  • C opens the consumable wheel. Same shape, five wedges, level-locked slots showing their unlock level.
  • Only one wheel is open at a time. Opening one closes the other. ESC closes whichever is active.
  • Right-click any wedge to lock the slot mid-run — lock to a specific item, lock to a category, or leave it free for auto-fill.
  • Slow-mo engages while a wheel is open, same as the old bag overlay.
  • Hotkeys 1–5 still work for fast consumable use. The wheel is the deliberate surface; the hotkeys are the reflex surface. Both are useful.

After the first playtest pass, the wheel ran 30% larger — the original size read well in isolation but was too small to use confidently in motion.

Protocol fixes

Several lingering bugs from the v0.71 protocol rollout closed this milestone:

  • Seeker dash charges — a registry rename left the wrong fallback sorting in place, giving the ability an extra charge. Fixed.
  • Rail Slug trail — the trail’s opacity was constant regardless of how long you held the trigger. Now it ramps with charge.
  • Sentinel cancels on re-press — pressing the ability key again during the placement phase now cancels it, same as Spectre. Previously the re-press was ignored.
  • Level-up picks offered unbuilt protocols — Flux and Swarm appeared in the level-up pool even though neither shipped yet. A new gate field on protocol definitions blocks unbuilt protocols from surfacing.
  • Protocol passives polled for state instead of listening for signals — three protocol passives were actively polling their bindings every tick instead of reacting to events. Converted to signal-based.

Other fixes

  • Practice mode was broken — picking Practice on the deploy screen sent you into a regular dungeon. A signal that routed the confirmation was discarded before it reached the mode selector. Fixed with a dedicated practice-run start path.
  • Hookshot survival across death — if you died mid-tether during the Vector encounter, the hookshot you were trial-equipping survived dungeon teardown and ended up permanently on the next run. The equip now revokes on death. Successful runs keep it.
  • Fresh loadout stat picks defaulted to the same value — opening the loadout for the first time left primary and secondary stat pickers pointing at the same stat, which then blocked the third pick. Defaults now differ.
  • Walking into your own deployable — the player was taking contact damage from their own turret or drone. Fixed.
  • Boss arena blank on first load — the camera was not committed before the first frame rendered on cold boss-room entry. A defensive fix landed; playtest confirmed clean.

What moved out

The numbers pass — XP curve, currency flow, gear sell values, loot rewards — was originally part of this milestone. It moved to v0.99 as a dedicated final-pass milestone before launch. The reason is practical: tuning those numbers is more useful after the intervening milestones have shipped more content to tune against. The design stays; the data waits for a better-grounded moment.

See pm/roadmap/zz_archive/0.80-visual-polish-and-numbers-pass/ for the full feature tree.

v0.79

The Archive is open

The Archivist and The Archive

There is now an NPC standing at the Quarantine pad. The Archivist does not run, does not fight, and does not explain itself beyond what the terminal requires. Approach and interact to open The Archive — a two-tab modal covering everything you have encountered and everything you have learned.

The Bestiary tab catalogs every enemy class you have sighted or eliminated. First encounter, kill count, and MEMORIAL-tier lore entries unlock as you accumulate records. Enemies you have only glimpsed carry partial records; enemies you have killed reveal the full file. Nothing is retroactive — the tracking began the moment this update loaded.

The System Memory tab holds your Memory Fragments. Bring the Archivist a fragment you have never seen before and it lands in the archive as a new record, tagged NEW until you view it. Bring a duplicate and the Archivist decompiles it into Echo currency instead. The ceremony is brief and clinical, as it should be.

Memory Fragments

Memory Fragments drop in combat rooms (low probability, any kill), from boss chests (moderate probability), and from Vector’s boss room in a fixed sequence tied to specific kill indices — mid-sentence, the count, the choice, in that order. The sequential drops are deterministic. They will appear on the same kills across every run. Whether that means anything is a question The Archive may eventually answer.

Echoes

The Echo wallet is live. Your current balance is visible in the HUD between Cycles and Keys. Echoes accumulate now via duplicate fragment decompilation; a spending path is not yet open. Consider this the accumulation phase.

Quarantine Pad Polish

The four corner pads dropped their [ OFFLINE ] labels — the state was self-evident and the labels were noise. The REQ PAD gained a proper pixel-art podium. The SIGNAL CACHE is now a pill-tube with an antenna pillar; the pillar blinks and shimmers when a level-up is available, goes dark when it is not. Two new palette entries (GameTheme.SignalCache, GameTheme.PadPodium) govern both.

Bug Fix — Quarantine Music

Level-up ceremonies were silencing the Quarantine ambient track and not restoring it afterward. The AudioOrchestrator now receives the ceremony-complete signal through Game and calls restore_mode_music on completion. The track comes back.

Enemy Naming Pass

The enemy roster received proper names matching the bestiary lore entries. Class-name placeholders like “mobile_shooter” and “turret_variant” are gone. If you have been modding .tres files by class label, the mapping is in the v0.79 feature notes. The in-engine enemy catalog and the Archive bestiary now refer to the same names.

Late polish (post-playtest)

A pass after archive caught the things you only see by actually using the new surfaces.

  • The Archive and the Requisition Terminal both gained an close button in the top-right and now auto-close when you walk away from the NPC. Either modal also closes on ESC or by clicking the dim area outside the panel — four ways out, none of which leave you stuck.
  • The Requisition Terminal lost its bottom bar; the cycles wallet moved up next to the close button, and the title now renders in the Programmer’s purple identity color so the chrome reads as that NPC’s terminal.
  • Approach prompt bubbles (the Archivist / Programmer / Breach Pad walk-up) were overflowing on multi-line dialogue and pushing the confirm / cancel buttons off-center. Bubbles now autowrap with a hard four-line cap, the confirm and cancel buttons stay centered on the bottom regardless of text length, and the bubble itself is a touch wider to breathe.
  • Quarantine pad labels (ARCHIVE, SIGNAL CACHE, PROGRAMMER, REQ PAD) sit cleanly above their pad rectangles instead of getting clipped by the outline. The Signal Cache pillar lies horizontally now — the antenna points east, into the room.
  • An Archive close path that stack-overflowed the engine (the close handler emitted the same signal it was listening to) was caught and fixed before merge. Filed and resolved in-milestone.

See pm/roadmap/zz_archive/0.79-archivist-bestiary-fragments/ for the full feature tree.

v0.78

One coordinated sweep of every screen you touch before a run — and it all ships together so you never have to relearn the same panel twice

Belt controls

Right-click any belt slot (in the loadout or mid-run with the B-key overlay) to get a small popup: Clear, Swap, or Lock. No more hunting for the right click target.

Locking a slot now means something real. The game has always tracked three states (free, locked to an item category like “Healing only,” or locked to a specific item like “always Frag Grenade”), but the UI only ever showed a binary toggle. A sub-popup on the Lock action finally exposes all three options. Auto-refill follows the same rule: when a locked slot empties, it pulls the first eligible item from your bag.

The in-run belt now matches the loadout belt visually. One row per slot with an inline lock indicator. The old two-row layout is gone.

Stash organization

All three stash panels — gear, charms, consumables — now have a sort/filter bar. Sort by type, rarity, name, or acquisition order. Filter by name substring or chip-select by type. Items group into collapsible rarity bands so a deep inventory stays readable.

Gear lock and sell

Right-click any gear entry in your stash to toggle a lock on it. Locked items refuse the sell action — a toast confirms why. Sell an individual piece with the [$] glyph on its row (payout scales to rarity), or hit “SELL ALL UNLOCKED” at the panel bottom and confirm.

Derived stats

Below the six primary stats on the loadout screen, a toggle reveals the full derived picture: max HP, dodge chance, fire rate, crit chance, lifesteal, thorns, all five resistances, and more — 31 values in total, pulled live from your current build. Worth opening after a gear swap to see what actually changed.

Level badges on locked slots

Gear, charm, and belt slots that haven’t unlocked yet now show a “Lvl N” badge so you know exactly when each one opens up.

Tooltip pass

Hovering a protocol in the level-up ceremony or the loadout picker now shows a full card: stat profile, active and passive descriptions, flavor text. Consumable tooltips match the visual quality of gear and charm tooltips — panel frame, icon, rarity header, and real effect copy (what it does, magnitude, duration, cooldown). Stat tooltips gained a player-facing description line; the eight primary stats each have a short prose entry explaining what they actually affect.

Screenshot button

Press F12 anywhere — title screen, in-run, menus, pause — to save a full-window PNG to user://screenshots/. The screen edges flash a brief green pulse after capture confirms the file landed. Remappable in Settings → Controls under a new “System” group.

Under the hood

The eight primary stats now have first-class data records (display name, description, color). Nothing visible changes yet, but they’re the foundation the HUD tooltip work in v0.89 will build on.

Deployable behavior data migrated from a legacy string-keyed registry to the same typed composition pattern enemies moved to in v0.76. The registry is gone.

The duplicate key-count display on the loadout stats bar is gone — one read, one place.

Fixes

  • CORE-1 dash silent since v0.71 — a registry collision between two protocol files meant the no-dash variant always won on a fresh save. Dash now resolves through the level-effect schedule, which had the correct answer all along but was never consulted.
  • Belt slots 3-5 rejected consumables after leveling up — the slot count cache wasn’t re-syncing when the stat layer changed. Fixed at the stat-changed hook so it stays in sync across every path that can add or remove a slot.
  • Charm replace-confirm dialog invisible — the confirm modal’s _ready was stomping the canvas layer set by the loadout host, pushing it below the panel. Modal now reads the layer from the scene file, hosts override cleanly.
  • Stat picker forced a third pick to swap primary and secondary — disabled items don’t emit selection signals, so the swap branch was unreachable. Both dropdowns now stay enabled; picking the other slot’s value swaps in place.
  • Hole-hazard respawn could place the player outside the room — clamped to room bounds, matching the existing hookshot respawn pattern.
  • Item tooltip stretched the full screen height — the autowrap description label was missing a width constraint. Fixed across all tooltip types.
  • Stash panels stopped scrolling after the sort/filter rebuild — the new grouped-rows widget was missing a ScrollContainer wrapper.

Known issue

Vector’s boss room intermittently renders with no walls, floor, or boss sprite — HUD draws correctly and simulation runs, but the playfield is visually empty. Instrumentation shipped to capture the wall geometry and visibility chain the next time it repros. Awaiting a log capture to pin the cause.

Smoke passes pending

Three windowed verification passes remain open: the belt-slot modal across both hosts, the belt management overhaul end-to-end flows, and the full tooltip parity alpha sweep.

See pm/roadmap/0.78-loadout-ux-pass/ for the full feature tree.

v0.77

Walk up to something

A text box appears. Walk away to dismiss it. That’s the whole interaction model — and v0.77 builds every interactive surface in the game on top of it.

The Programmer

The Quarantine’s first NPC is live. The Programmer is a brand-purple circle parked near the hub’s north corner. Walk into their space and a terminal bubble floats above their body: “Welcome, [protocol]. What can I code up for you?” with Enter Req. Terminal? ✓ ✗. Confirm to open the gear shop; walk back out when you’re done and a goodbye line plays. Walk back in and the full cycle runs again. The Requisition Terminal is now the primary shop — a diegetic object you approach, not a menu option.

Approach-driven interaction everywhere

Everything interactive in the game now uses the same pattern: walk into range → prompt appears over the entity, with ✓ / ✗ options where a choice matters → walk away to decline. No click-to-talk. No button-to-open-dialogue. Walk-away is the cancel verb.

Three surfaces migrated this release:

  • Treasure chests no longer auto-open on contact. Walk up and the prompt appears — “Open chest? ✓” for a standard chest, “Use key? ✓” for a locked one. The key-consume path, the pour ceremony, and the chest rarity tiers are all unchanged; only the trigger changed.
  • The breach pad in the Quarantine hub now prompts before dropping you into the root. The contact trigger it used before is gone.
  • The Programmer (above) is the first NPC to consume the new surface.

Terminal dialogue bubble

All prompts use a new text box styled for NullBound: squared corners, terminal monospace font, and a per-character type-out animation (40 characters per second by default). Pressing confirm or the shoot input while text is revealing completes the reveal instantly; a second press advances to the next beat. The pattern should feel familiar if you’ve played a text-heavy JRPG and it should look like it belongs on a terminal.

Terminal cursor

The OS default cursor is gone. NullBound now ships a narrow brand-cyan triangle with a sharp tip — a paper-airplane silhouette that matches the game’s aesthetic. It swaps to a light-gray variant when hovering over clickable buttons. After the first playtest pass revealed the original cursor was too large, it shipped at half the initial size with anti-aliased edges.

Under the hood

Every regular enemy now perceives threats through a per-entity detection zone rather than a global shared candidate pool. Behavior is unchanged — same threat scoring, same dwell timers, same attack patterns — but each enemy now has a tunable awareness radius and LOS is computed once per entity rather than redundantly inside each behavior. Vector (the T1 boss) retains its room-wide omniscience: it always sees you anywhere in the arena, which is the same behavior as before, now running on the same infrastructure as every other enemy.

The interaction and perception code was also reorganized into self-contained module folders with documented public surfaces — a structural cleanup that makes the system easier to extend as more NPCs ship.

Polish

  • Spectre weapon: the charge rifle now has an unlimited magazine. No reload UI exists for it, and the janky reload state that surfaced during playtesting is gone.

See pm/roadmap/0.77-approach-interaction-system/ for the full feature tree. PR #31.

v0.76.3

CI hotfix + spec cleanup

The release.yml “Build release notes” step still tried to awk product/CHANGELOG.md, which doesn’t exist after the v0.76.2 changelog refactor — every release-build since the refactor failed silently, no GitHub Releases were cut, and the marketing site’s /releases/ page showed v0.76.1 as the latest version. The step now reads product/changelog/ directly, parses YAML frontmatter for the version array, and renders every entry in the v{MAJOR.MINOR} family sorted descending. CORE-1’s spec also got its blockquote tagline aligned with the other five protocols (“Whatever it takes.” instead of the descriptive sentence that previously sat in the slot). No game-side changes.

v0.76.2

Changelog refactor

The single product/CHANGELOG.md file decomposes into a product/changelog/ directory — one markdown file per release, with YAML frontmatter driving sort + display label. Concurrency-safe (no merge conflicts when two branches add notes), file-as-unit matches how releases ship, and mirrors the pm/roadmap/ milestone tree pattern. Authoring contract lives in product/changelog/README.md. The “What this game does” overview moved to OVERVIEW.md as a new ## Systems section. No game-side changes — paired with the website’s build_releases_page.py rewrite to read the directory.

v0.76.1

CI hotfix

release.yml was calling a workflow that doesn’t exist (deploy-marketing-site.yml) instead of the actual website-rebuild workflow (dispatch-website.yml). Surfaced during v0.76 close-out — the wrong filename produced an HTTP 422 from GitHub on every merge to main, silently breaking /release-notes updates on the marketing site since the workflow shipped. No game-side changes.

v0.76

The T1 dungeon is now a complete combat loop from first room to final boss

Phase 1 — Enemy Roster (9 new enemies)

The dungeon has a full cast. Nine enemies across five archetypes join the roster, each with distinct behavior compositions and room placement weights:

  • Mobile Shooter — kites at mid-range, retreats when threatened, holds position while firing.
  • Sniper Turret — stationary, projects a red target-lock beam before firing a high-damage bolt at long range.
  • Melee Rusher — closes on the player and delivers contact damage; the template all rushers share.
  • Gun Rusher — same chase pattern as the melee rusher but opens fire once it reaches its engagement ring.
  • Grenade Rusher — closes fast and lobs grenades once in range; borrows the shared ThrowableConfig layer.
  • Gun Tank — advances to medium range and sustains a high-damage-per-second barrage while repositioning slowly.
  • Daemon — doesn’t attack directly; projects an aura that amplifies the damage of all nearby enemies; max one per room.
  • Shield Tank Elite — elite variant of the gun tank; a frontal shield rotates to face the player, blocking all forward fire; flanking is required to deal damage.
  • Daemon Elite — elite daemon with an upgraded aura and altered stat profile.

All nine enemies use rate-limited aim (aim_turn_rate_rad_per_sec) so they slew toward the player rather than snapping instantly. Enemy fire is now gated on aim-on-target — enemies won’t shoot while still rotating toward you.

Phase 2 — Content Triage

The item roster sharpened. Three deployables were cut (shield bumper, gravity well, speed ramp) — each had design or feel problems that weren’t worth iterating on now. Two consumables were cut (cycle boost was out-of-vision for the run economy; gravity grenade felt like a worse frag grenade). What remains is tighter. Changes to what stayed:

  • Void Spike fixed: now pulls enemies toward the impact point, applies a slow, and shows a visible tether on the target throughout the bleed duration.
  • Repulsor Field redesigned: no longer pushes everything — it now creates an enemy-only slow field, making it a defensive zone rather than a chaos tool.
  • Powerups tuned: pickup duration extended to 5 minutes, stack cap 3 with linear intensity scaling (no diminishing returns). Enemy and elite kills now drop powerup orbs at a meaningful rate. Orbs shrank 50% visually and gained a coin-flip idle animation. HUD shows stack-count badges per powerup slot.

Phase 2 — Treasure Rooms

Standard dungeons can now contain a locked treasure room. A small key (dropped by enemies, found in rooms) opens the door. Inside: a treasure chest with a tiered loot pour scaled to rarity. Chests open with a multi-phase ceremony and a per-tier shimmer shader. Boss rooms spawn a boss chest at arena center after the boss dies.

Phase 3 — T1 Boss: Vector

The first boss is Vector, the Null Guardian. Vector is a large undulating triangle that guards the final room of the T1 dungeon sequence. Its phases escalate:

  • Phase 1: patrols and fires a ring-blast weapon; hookshots to relocate.
  • Phase 2: charging spin telegraph, glitch-slice attack, and increased ring-blast cadence.
  • Phase 3: full aggression — edge distortion shader pulses on the body, all attacks in rotation.

Vector’s arena features a continuous north-to-south fall pit with three floating platforms; crossing the arena requires using the hookshot to traverse. Standing in the pit deals fall damage and teleports you to the nearest platform edge.

The boss health bar appears at the bottom of the play area on boss room entry and fades out after Vector dies. It hides automatically in Practice and Quarantine modes.

Two title cards play on boss room entry: “Null Guardian — Vector” and “Hookshot Unlocked” (if this is the first hookshot encounter).

Phase 4 — Polish

  • Dodge feedback: when the player’s dodge-chance stat avoids a hit, a DODGE popup appears at the player’s position in the GameTheme.CombatFeedback palette color.
  • Spectre improvements: a glowing cyan anchor marker now appears at the Spectre’s dash origin point; the drone slows enemies in its AoE by 50%; charge-shot tunneling through enemies fixed.
  • Respawn polish: CORE-1 now parks at the REC. PAD synchronously on Quarantine entry — no more sliding in from off-screen.
  • Combat color palette unified: crit, dodge, shield absorb, heal, and default damage each have a dedicated GameTheme.CombatFeedback color token.

Mechanics

  • Per-hit crit roll: DamagePayload now carries crit_chance + crit_multiplier authored on the spec; the receiver rolls roll_is_crit() per hit rather than stamping the payload at fire time. Pierce shots produce mixed crit/non-crit outcomes instead of all-or-none.
  • Hookshot airborne state: the player drops the PLAYER_GROUNDED collision layer during hookshot flight, making the player immune to fall hazards while in the air. Other detectors (bullets, walls, trigger zones) are unaffected.
  • Enemy omniscient targeting: EnemyDefinition.omniscient_room_vision allows boss-tier enemies to target players anywhere in the room regardless of line-of-sight.

Late polish (post-playtest)

  • Boss success flow: when Vector dies, the arena dissolves (cover walls + pit + platforms), a boss chest spawns at room center with a BOSS-pour ceremony, and a “Return to Quarantine” pad fades in at the top-left. Walking onto the pad opens a “Signal Restored” run summary with distinct title and subtitle copy from the death menu’s “Null Advances”. The action button reads “Return” on success, “Retreat” on death.
  • Fall ceremony: falling into the boss pit now plays a real beat — character shrinks as if falling, takes damage, then re-materializes at the nearest platform edge via the recreation dissolve shader (faster than the Quarantine recreation; same visual vocabulary). Cooldown semantics tightened: only arms after the player exits the pit, so teleport edge-cases re-trigger immediately.
  • Hookshot + falls: mid-hookshot the player drops PLAYER_GROUNDED and is airborne-immune to falls. If a hookshot ends mid-pit, the fall fires immediately on grounded restoration (no walking-on-pit free pass).
  • Melee rusher swarmer pattern: melee rushers now dart in, hit, and back off ~130px before re-engaging — bee-style swarmers instead of glue-stickers. Multiple rushers desync naturally and take turns engaging.
  • Daemon support pivot: daemons now seek the densest cluster of allies and park within their aura range rather than chasing the player. The aura’s buff/heal effect is unchanged; the positioning is what changed. Solo daemons hold position.
  • Run summary signature items: items unlocked DURING a run (e.g. first-time Hook Shot acquisition) appear in a new “Signature Items” section. Subsequent runs that already own the item show no entry.
  • Z-layer convention codified: GameProperties.ZLayer now centralises render-layer constants (HOLE / FLOOR / ENTITY / PROJECTILE / HOOK_VISUAL / VFX / HUD_OVERLAY). Pure dev-facing — fixed a platform-over-entity layering bug and prevents the next regression of the same shape.

See pm/roadmap/zz_archive/0.76-t1-combat-loop/ for the full feature tree.

v0.75

The Quarantine exists

Every run starts and ends there.

Launch the game, see a trimmed Main Menu (Deploy / Settings / About / Exit), pick from up to three save slots with a long-press delete guard, and land in a walkable hub: four labeled floor pads tucked into the corners (ARCHIVE green / SIGNAL CACHE gold / PROGRAMMER purple / REC. PAD cyan), a breach gap in the north wall with an “ENTER /root” label, and hub-distinct music routed through a new AudioOrchestrator. Walk into the breach, confirm, and the existing dungeon run launches.

Die — and instead of a menu cut, CORE-1 re-assembles on the REC. PAD: a custom dissolve shader resolves the procedurally-drawn player out of noise with a brand-cyan tint, a respawn flash bursts at the player’s position alongside the “Welcome back, [protocol]” card, and input releases once the flash reads so you walk freely while the card finishes on its own timeline. Walk to the SIGNAL CACHE pad and any pending level-up ceremony opens in place; the pad pulses gold when levels are waiting.

Tactical reads: belt and active abilities are locked in non-COMBAT rooms (Quarantine and dungeon safe rooms); pause menu says “Quit to Quarantine” when that’s where you came from.

Internal: QuarantineMode is a clean ModeController plug-in alongside the existing DungeonMode and PracticeMode; GameManager caches _previous_mode_class so the swap-back is mechanical rather than bespoke. Title-card infrastructure (TitleCardConfig Resource + TitleCardManager + TitleCardRegistry) ships as general-purpose foundational infrastructure, driving four v0.75 cards (“The Quarantine”, “T1 Dungeon · Good luck, [protocol]”, “Welcome back, [protocol]”, “Sanctuary”) with a SCREEN_UV glint shader on fade-in, {key} substitution, per-card SFX + music-duck hooks, and a queue/interrupt lifecycle that is pause-aware.

Three CC BY 4.0 music tracks added (Amarent “Just Wondering” as the Quarantine theme, Joseph Sacco “Electronic Crystals”, Fretbound “Electronic Inspirational”). GameTheme restructured: Primary is now Primary.Cyan + Primary.Purple (the two formal identity tiers); Brand class removed.

See pm/roadmap/0.75-quarantine-foundation/ for the full feature tree.

v0.74

Practice mode is now a true sandbox: snapshot-clone your loadout on entry, step on a refresh pad to reset, die and come back — no penalty, no run counter, no timer

Room geometry is right: E/W doors are proportional, shared walls no longer double-thick, door panels block bullets in both open and closed states (two independent pre-existing gaps, both fixed).

Run over? The new Run Summary screen shows what actually happened — cores, rooms, kills, consumables, loot, and cycles — with title and accent color tuned to whether you died, timed out, completed, or quit. Replaces the static “NULL ADVANCES” panel for all four end paths.

Internal: ModeController abstraction splits game-mode orchestration out of GameManager, making v0.75 Quarantine a clean plug-in. Run log event stream (DungeonRunLog + RunLogStore) instruments every run for future server validation and balance data.

v0.73.16

Planning content carried across

Every previously-tracked feature now has its scope, ship criterion, and stories sitting next to it in the new tree — 21 features fleshed out, 87 stories filed under their parent feature, 14 open bugs anchored to the milestone where they were caught. The Jira shim that powered the import is gone. No player-facing changes.

v0.73.15

Back-of-the-house planning

No player-facing changes. The roadmap moves out of Jira and back into the repo as a structured filesystem tree under pm/roadmap/ — one directory per milestone, self-contained, with locked-down YAML frontmatter on every doc so the whole thing is grep-friendly. The road through release is now fully planned: 14 pre-launch milestones (alternating combat and identity tracks), the launch cut, and three post-launch drops (Swarm, Flux, alt distribution). What’s coming next: the Quarantine hub and the title-card system in v0.75, the T1 dungeon content settling pass in v0.76, and the first NPC + diegetic shop in v0.77.

v0.73

Codebase audit and cleanup before alpha

No player-facing changes: signal boundaries tightened, dead code deleted, 370 lines of duplication cut, and the planning surface migrated to Jira. Sets the table for everything that comes next.

v0.73 infra

Devlog reach

Landing page gains a Devlog nav link, release notes auto-link the matching devlog post, and Bluesky now announces new devlog entries alongside the existing Discord channel.

v0.72

Local multiplayer plumbing

Input architecture decoupled from player identity — one ControlScheme per player, rebindable keyboard / mouse button / gamepad button, slot-assignment screen picks schemes + devices for up to four players, and a multi-PlayerCharacter spawn. Camera and HUD stay single-screen centered on P1 this release; split-screen and per-player HUD land in a future milestone.

v0.71

Four alpha protocols, four different feels

CORE-1 scavenges, Sentinel digs in behind armor and a barrier, Seeker slips into stealth on dash, Spectre charges a sniper round and marks targets with a drone. Crits now read on screen — bigger bolder numbers, a bright hit-flash, and screen shake scaled to the hit.

v0.71 infra

NullBound has a home on the internet

One-page landing site at nullbound.signalandecho.com with game overview, key art, and an alpha signup. Push to main and the site redeploys on its own.

v0.70

Stat builds come alive

Agility scales weapon damage and fire rate, Focus amplifies crits and cuts cooldowns, Speed makes you fast and stretches deployables, Luck spikes crit chance and loot rarity. Every stat now matters.

v0.69

Build stacking

Charms, gear, equipped elements, and timed buffs all inject weapon modifiers at the moment of fire — homing rounds, crit boosts, element swaps all compose cleanly. Q cycles your channeled element mid-run.

v0.68

Weapons are built from swappable parts

Any chassis + trigger mechanic + projectile + damage profile + stat-scaling composes into a unique weapon. Bullet feedback bar shows reload, charge, overheat, and cooldown state at a glance.

v0.67

Threat-weighted enemy AI

Enemies target by perceived threat, not “always chase the player.” Decoy flares pull aggro, high-threat turrets draw fire, and zero-threat equals full invisibility. Hazard and consumable polish pass bundled.

v0.66

UI palette unified

Consistent colors across loot, ceremony, HUD, and rarity indicators.

v0.65

Deployables, hazards, and throwables all ship as data

Frag grenades, gravity grenades, void spikes, leech spikes, sniper turret, gatling turret, gravity well, repulsor field, speed ramp, null barrier, shield bumper, repair drone — eight deployables across offense / defense / control.

v0.64

Locked doors and keys

Combat rooms seal on entry, unlock on clear. Keys persist across runs.

v0.63

Consumable belt

Two-slot belt with strict type taxonomy; stash-click fills to cap.

v0.62

Gear & stash UI

Two-step equip flow with rarity-colored borders, type glyphs, and modifier tooltips.

v0.61

Loot drops

Gear and charms drop from enemies with deterministic seeded RNG, rarity-colored pickup orbs, and charms as gear sockets.

v0.60

Deployables get composable behaviors, shared targeting, and entity pooling

v0.59

Consumables ship as data

New items need a .tres, not new code.

v0.58

Status effect polish

Threshold scaling, immunity cooldowns, unified buff/debuff bar.

v0.57

Loadout panel redesign

v0.55

Particle VFX

Hit sparks, death bursts, status shimmers, muzzle flash, arc crackle.

v0.54

Enemy pathfinding

Nav-mesh movement with straight-line fallback.

v0.53

Elemental status effects

Overload, Decay, Arc, and Corrupt trigger on buildup thresholds.

v0.52

Elemental combat

Four elements with a matchup table and receiver-calculates damage resolution.

v0.51

Room and dungeon templates

Data-driven starter content.

v0.49

Spatial room layout

Rooms at real world coordinates, three-dungeon sequence (entry → main → boss), SubViewport rendering, room-locked camera with smooth pan.

v0.47

Signature items

Boss tier unlocks with trial equip on entry, permanent unlock on defeat. Hook Shot ships first.

v0.46

Protocol dashes + unified effect system

Six protocols × three tiers of unique dash behavior. Every stat, capability, and buff now flows through data-driven effect files.

v0.45

Signal Strength leveling 1–50

XP curve, level-up ceremony, gear & charm equip API.

v0.44

Consumable system, powerup rebuild, deployable audit

v0.43

Procedural dungeon generation, minimap with fog of war, inventory & loadout UI, persistence

v0.38–v0.42

Dungeon architecture, player shooting, enemy entities, bullet patterns

v0.31–v0.35

HUD, countdown timer, player stats

v0.001–v0.30

Engine foundation and MVP proof