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.