Patch Notes · Dispatches from the Dungeon

The Chronicle

What we've built, what we've broken, what we've quietly buried on floor 4. Newest first.

◀Page 9 of 11 · v0.41.0 — v0.32.1▶
v0.41.0#April 27, 2026Feature

The Hub Comes Alive

The overworld used to be the game's loading screen with a person on it. Procedural canvas tiles in five flavours of green, props painted with fillRect, and a shopkeeper outpost that was nothing more than an [E] prompt floating in empty grass. This week the hub got a texture pass and the merchant actually showed up.

Each biome zone now skins itself with the matching dungeon theme's PNG floor pack — forest tiles in the meadow and woods, dungeon stone in the ruined zone, crypt withering in the north, void cracks in the east, and cave scorch in the southeast. Four 32×32 variants per theme rotate per tile by xy hash, so the surface looks varied instead of repeating in obvious rows. Prop scatter pulls from the same packs: mushrooms / logs / vines in the forest, barrels and dropped swords in the ruins, coffins and skullpiles in the crypt, runes and spikes in the void, crystals and stalagmites in the hellpit. Trees stay canvas-painted (no PNG art for them) and layer in front of the PNG ground props for canopy depth.

The shopkeeper outpost now has a building. Closed wood door in the middle, hand-lettered sign hanging above, two flanking brass lanterns flickering on a 3-frame loop, and a softly breathing additive halo behind each lantern so the shop reads as warm even from across the meadow. The merchant himself stands in front of the door with the same idle animation his dungeon counterpart uses, and the [E] prompt now hangs over him instead of the door so the approach feels like greeting a person, not opening a building.

Pressing [E] opens the actual shop UI — Buy / Sell / Identify tabs, gold bar, the same per-floor stock generator the dungeon merchant uses. Floor 0 stock is mostly potions and low-tier gear, which fits "hub merchant" perfectly. Sell and Identify tabs work against your full inventory, so the hub is now where you offload junk between runs.

Water and lava picked up some atmosphere. The river drifts white sparkles westward at 90ms cadence, additive-blended so they catch against the dark water. The hellpit lava strip exhales orange-to-red embers rising at 70ms cadence with random horizontal sway. Two particle emitters total — single GameObjects, GPU-accelerated, no per-tile sprite churn.

Lanterns no longer pulse in lockstep — every animated prop (path lanterns, magma vents, void shards, shop lanterns) carries a per-sprite phase offset so a row reads as separate flames instead of one ticking metronome.

All five dungeons unlock by default. The unlock-on-clear chain (Forest → Ruined → Crypt → Void → Hellpit) is gone — pick whichever gate matches the run you're in the mood for.

What landed

  • Asset-driven overworld bake — bakeOverworldMap(scene) reads PNG floor + prop textures from the scene's cache. Biome → theme map: meadow/forest = forest, ruined = dungeon, crypt = crypt, void = void, hellpit = cave. Water / mountain / shore stay procedural (no PNG art).
  • Shop outpost dressing — door (frame 0, closed) + sign tile + two prop_shop_lantern sheets (3-frame flicker, 6fps, per-sprite phase offset) + two prop_shop_lantern_glow halos (additive blend, alpha-tween 0.55↔0.85 over 1.2s).
  • Overworld shopkeeper NPC — npc_shopkeeper sprite with a 4-frame idle-down animation, positioned 1.1 tiles in front of the door so the player approaches him head-on.
  • Shop UI hooked up — pressing [E] near the merchant calls setShopOpen(true) directly, opening the same Buy / Sell / Identify modal the dungeon shopkeeper uses. Closing the modal emits gate-card-closed so the proximity lock releases for the next [E] press.
  • Ambient particles — single river-spanning sparkle emitter (90ms drift west) + single lava-strip ember emitter (70ms rise with horizontal sway, tint cycle yellow→orange→red). Both additive-blended.
  • Lantern desync — every animated prop carries a phase data value derived from its position; the update-loop frame index uses (time + phase) * fps. Path lanterns, magma vents, void shards, shop lanterns all desync independently.
  • All dungeons unlocked by default — every entry in DUNGEON_TYPES now has unlockRule: { kind: "default" }. The cleared-prerequisite chain is removed.

Bugs killed

  • Shopkeeper missing from Crypt / Void / Hellpit — the spawn gate read level.bossLevel from the LEVELS theme table, where Cave / Crypt / Void are flagged bossLevel: true for the legacy 17-floor rotation. Themed dungeons gate-skipped on every floor. Replaced with isCurrentFloorBossLevel() which reads dungeon.floorCount - 1 from the active dungeon definition.
  • Trees disappeared in the forest — the texture-pass swap replaced canvas-painted trees with PNG mushroom / log / vines, but the PNG pack ships no tree art so the canopy went flat. Trees come back via the canvas painters; PNG props layer underneath as ground-level dressing.
  • Shop tile was empty — the shop position only rendered the [E] prompt floating in air; no door, no sign, no merchant. Replaced with the full outpost dressing + NPC sprite.
  • "Coming soon" stub on shop press — the shop variant of HubFurnitureModal showed placeholder copy. Replaced with a short-circuit that opens the real shop modal directly.
v0.40.0#April 27, 2026Feature

The Run Has an End

A themed dungeon used to descend forever. You'd kill the Forest's boss on floor 3, step on the stairs down, and find yourself on floor 4. Then 5. Then 6. The "completion" never registered, the next gate never unlocked, and there was no way back to the hub short of dying. This week the run actually ends.

Stairs down on the boss floor of a themed dungeon now check whether the boss is dead. Alive — the descent is gated with "Defeat the boss first." Dead — the screen fades, the run record posts as complete, and you're back at the meadow with the next-tier gate now lit. The unlock travels through the leaderboard pipe, so the Hall of the Fallen and the next dungeon's gate state both update.

The hub frame got bigger. The whole game window now fills the viewport (still inside the double golden border), the Phaser canvas renders at 1:1 to the screen instead of envelop-scaling a 640×480 letterbox, and the camera zooms 2× so sprites stay the chunky size they were. Net effect: the same hero, way more of the world visible at once.

Stamina has weight now. Sprint drains the bar twice as fast as before — the full pool empties in two seconds of running, not four. Heavy attacks cost 25 stamina, so you can't spam them in a sprint+heavy combo and never run dry. The exhaustion lockout that was supposed to gate sprint until 25% recovery actually works now (a threshold off-by-one let stamina bounce around 1 and re-engage sprint forever). And the cap itself now scales with VIT — every point of vitality adds 10 max stamina on top of a 50 base, so an endurance build can sustain more sprint windows + more heavy chains than a glass cannon.

The character sheet expanded — the stats / skill tree / trophies modal now stretches up to 1100 px or 80% of the viewport (whichever is smaller) so the trophy grid doesn't crush itself into a column of two on widescreen.

What landed

  • Run completion + unlock chain — themed dungeon final-floor stairs gate on boss death, fade-back-to-overworld submits the run as completed: true, leaderboard's recordDungeonClear registers the next-tier unlock.
  • Anti-cheat completion gate fixed — the legacy 17-floor rule flagged every themed-dungeon clear as invalid (Forest=3, Hellpit=5). Now reads floorCount - 1 from the dungeon definition; legacy / endless runs keep the 17-floor rule.
  • Scale.RESIZE + camera zoom 2× — game canvas matches its parent in real pixels, camera zoom keeps sprites visually 2× their native size; growing the chrome reveals more world, not bigger sprites.
  • Full-viewport chrome — PlayWindowChrome switched to flex column with the gameplay area as flex-1; the page itself is h-[100dvh] overflow-hidden. Decorative torch backdrop + PlayHeader + PlayFooter dropped — /game is just the nav and the game window.
  • HUD transparency — main status panel + multiplayer badge
    • HUD buttons dropped from bg-black/90 to bg-black/65; minimap from 0.85 to 0.55 with a 6px backdrop blur.
  • Character sheet width — modal grows to min(1100px, 80vw) instead of the old 560px cap.
  • Stamina drain doubled + heavy attack cost — sprint at 50/s (was 25/s); heavy attacks cost 25 stamina and gracefully fall back to a light swing when the pool is too dry.
  • maxStamina from VIT — baseMaxStamina = 50 + vit * 10, recomputed on level-up and skill-unlocks; current stamina scales proportionally so a level-up bump doesn't snap the bar to "exhausted."
  • Save in overworld — new endActiveRun store action clears the dungeon tag + seed cache and flushes the save. Continue on a hub-state save now boots into Overworld instead of trying to resume a finished run.
  • Menu polish — WSAD navigation alongside arrows, Space + F confirm alongside Enter, and a session-pending gate kills the brief "Sign In" flash that appeared before the auth check resolved on returning logged-in users.

Bugs killed

  • Forest doors latched closed on revisit / forever-descending Forest — both downstream of the missing run-end. Floors past the declared depth were freshly generated each time with no cache; the player would assume "doors I opened are now closed." With the run actually ending at the boss floor, the issue doesn't surface in normal play.
  • Bridge stuck the player — the river-crossing gap is one tile wide and the player physics body was the same size, zero tolerance for diagonal drift. Body shrunk to 20×20 with a (6,6) offset.
  • Death modal was unclickable — the rank-reveal scrim inherited pointer-events: none from the GameHUD root and never opted back in. Same fix as gate cards last week — pointer-events: auto on .rr-scrim.
  • Sprint never actually exhausted — wantsSprintMove had a stamina > 1 threshold; stamina drained to ~0.7, sprint disengaged for one frame, regen ticked back to ~1.3, sprint re-engaged. Bounced forever, never hit 0, exhaustion flag never latched. Threshold is now > 0; exhaustion is set atomically when sprint drain clamps at 0.
  • Page crash returning to the hub — OverworldScene's class fields (gates / furniture / animSprites) only initialize on new, not on subsequent scene.start("OverworldScene"). The second hub entry was pushing new sprites onto stale-handle arrays. Game-level event listeners (enter-dungeon, gate-card-closed) were also stacking — anonymous handlers with no detach. Both fixed with explicit reset at the top of create() and named listeners that detach on SHUTDOWN.
  • Cross-run state bleed in dungeons — floorCache + doorSprites weren't cleared on GameScene.create(), so a second run inherited the previous run's open-door / opened- chest snapshots on floors with the same numeric index.
v0.39.0#April 26, 2026Feature

Walk the Shardrealm

The hub introduced last week was a flat green floor with five billboards. This week it's an actual place. Three times the size, nine biome zones, an east-west river you cross on plank bridges, mountain ridges along the north and west, ocean lapping at the south and east edges, a magma stream feeding the Hellpit's eastern bend.

You spawn in a meadow clearing. Walk south through long oak shadows lit by lanterns to reach the Forest Gate. West through stone rubble and rust-streaked boulders to the Ruined Halls. North past skull cairns and dead trees stitched with fog patches to the Crypt. East across cracked ground glittering with cyan-violet shards to the Void Rift. Far southeast, beyond a basalt-strewn plain that breathes ember haze, the Hellpit Caverns. The dirt path always points the way.

Your hero is normal-sized now. The gates are doorways instead of billboards. The map's about three times what it was, so you can't see all five from the spawn — finding them is part of the journey.

The hub furniture started doing things. Walk up to the memorial monument and press [E] to see your Hall of the Fallen — every hardcore character whose run ended badly. The lore stone pulses softly; press [E] and it tells you about the Shardrealm in your own language. The shopkeeper outpost is still empty — that's a future patch.

A minimap sits in the corner now. It paints the same biome washes as the world below, the five gates as theme-tinted dots, your position as a gold blip. The gate you're closest to pulses so you don't have to wait for the [E] prompt to know which dungeon you're about to commit to.

What landed

  • Overworld v2 — 96×72-tile map (~3× Sprint 36), nine biome zones, river, mountain ridges, ocean periphery, dirt paths, bridges, lava stream, prop scatter (trees / boulders / cairns / dead trees / shards / vents). All baked once into a single map texture so frame budget stays in dungeon territory.
  • Animated hub props — lanterns flicker (8fps), magma vents ember (8fps), void shards pulse cyan↔violet (4fps).
  • Terrain collision — water, mountain, lava, and ocean block movement; bridges punch walkable holes in the river and lava barriers so the dirt paths actually function.
  • Hub minimap HUD — top-right corner, biome-tinted, player blip with glow, last-visited gate highlighted.
  • Memorial / lore / shop interactions — [E] near each opens a popover. Memorial pulls your hardcore deaths from the run history. Lore stone reads the intro chapter inline. Shop is a stub for the cosmetics system.
  • Player + gate scale at 1× — Sprint 36 had everything at 2×, which made the hub feel like a billboard yard. The smaller hero in the bigger world reads as exploration.
  • Hub BGM — the forest theme plays in the meadow, fades out cleanly when you commit to a gate.

Bugs killed

  • Couldn't enter dungeons — the gate card popover sat outside the game frame because of how the modal scrim was anchored. Fixed; modal slides up inside the canvas now.
  • Modal was un-clickable — the same popover ignored mouse input on first ship because GameHUD's root overlay opts out of pointer events. Modal now opts back in explicitly.
  • Player stuck on the path to the Forest Gate — collision rectangles built from default-origin sprites were shifted 16 px and collapsed the bridge gaps over the river. Rebuilt with explicit (0, 0) origin so the bridges open properly.
  • Sprint never ran out — stamina ticked from 0 → 1 every frame, which immediately re-engaged sprint and effectively gave infinite stamina. There's now an exhaustion lockout: drain to zero and you have to recover to 25% before the bar lets you sprint again.
v0.38.0#April 26, 2026Feature

Pick Your Descent

For 35 sprints, every run started the same way: hit New Game, pick a class, watch a random dungeon roll out underneath you. This week the meta-loop changed. After Class Select you now spawn in an Overworld hub — a peaceful forest clearing with five themed dungeon gates around it. You walk up to one, press E, and a card slides up showing the dungeon's name, difficulty stars, bestiary preview, your personal best, plus the difficulty + hardcore picker. Confirm and you drop into that dungeon's themed run.

Five dungeons, each one a coherent identity:

  • Emberwood Grove (T1) — easy entry, friendly bestiary, +20% gold drop. The starter.
  • The Ruined Halls (T2) — abandoned keep, balanced. Unlocks once you clear the Grove.
  • Sunken Ossuary (T3) — undead-heavy, +20% rare drop. Bones, dust, and a dry wind that whispers names.
  • Void Rift (T4) — caster-heavy, +30% epic drop. A tear in the world. Things still live here.
  • Hellpit Caverns (T5) — five floors, +50% legendary drop. Magma seeps from the walls. The trials end here.

Themes used to be a hidden randomness — different floor = different theme, no continuity. Now they're a strategic choice. Crypt run? Skeletons / mummies / necromancers, every floor. Void run? Wraiths and casters all the way down.

Each themed clear unlocks the next tier and grants an achievement (Cleared the Grove / Cleared the Ruins / Master of the Crypt / Survived the Void / Hellpit Conqueror). The leaderboard now scopes by dungeon too — every category you see can be filtered to just one dungeon's runs.

The death loop changes too. Used to be: die, restart same dungeon. Now: die, return to the hub, pick a different dungeon for the next attempt. The hub is your post-run home — a memorial monument tracks your fallen heroes, a lore stone hints at the world (more here in a future sprint), a shopkeeper outpost is a stub for cosmetics that'll come with their own sprint.

Multiplayer keeps its existing flow — room codes still drop parties straight into a run with random theme, no hub. The hub is single-player only.

What landed

  • Overworld scene at hub-entry — forest backdrop, 5 themed dungeon gates radiating from a center clearing, WASD walk, E approach prompt. Memorial monument + lore stone furniture. Animated gates: forest pulses 8fps, void rift swirls 12fps, hellpit embers 8fps, crypt haze 4fps, ruined static.
  • Gate Card popover — pixel-art header, theme accent, difficulty stars, bestiary preview, personal best, daily- challenge ribbon when applicable, run modifier picker, locked state with prereq hint. Esc closes; Enter confirms.
  • Themed dungeons — every floor of a themed run uses a single theme + curated bestiary + forced final boss. No more theme rotation per floor.
  • Per-account unlocks — clear a dungeon (any class, any difficulty) to unlock the next tier. Forest is unlocked by default. Anonymous players get every dungeon unlocked client-side; logging in syncs back to the server.
  • 5 new achievements for clearing each dungeon.
  • Leaderboard dungeon filter — /leaderboard?dungeon=void scopes any per-run category to a single dungeon's runs.
  • Schema migration 0008_dungeon_types — run_record gains dungeon_type_id; user_dungeon_unlock table tracks per-account progression.
v0.37.0#April 26, 2026Feature

Plant Your Torch on the Wall

Every run you finish now lands on a global leaderboard. Eleven categories, four classes, four time windows, and a daily challenge where everyone runs the same dungeon. The deepest descent, the fastest clear, the biggest crit, the meanest hardcore death — there's a niche on the wall for whatever your play style is.

The new home is /leaderboard. Eleven category pills across the top — Deepest Floor / Fastest Clear / Longest Run / Kill Count / Gold Earned / Highest Level / Legendaries Found / Biggest Crit plus three per-account aggregates (Achievements, Total Boss Kills, Total Runs). Filter by class, flip on Hardcore Only, scope by time bucket (All-Time / This Month / This Week / Today). Top 3 sit on a podium with tier-tinted glow — gold for #1, silver for silver, bronze for bronze — and ranks 4–50 fill the table below. Every filter combo lives in the URL, so sharing a deep-link is just copy-paste.

Your public profile page at /u/<you> got a new section called Best Ranks, slotted between Featured Character and Stats. It shows your top 3 strongest categories — class-tinted card per rank, big pixel rank number with a rarity tier dot, "Top 50"-style badge in the body. Hover lifts the border to gold and reveals a "View on leaderboard →" CTA. If you haven't ranked anywhere yet, the section folds into a single dashed CTA pointing back to /game.

Right after every run — death or victory — a modal slides in showing where you placed. Run summary on top (floor, level, kills, duration), then the rank reveal block tinted by tier: gold for top 3 with a Trophy podium glyph, silver for top 10, bronze for top 50, no glow past 50. #1 specifically fires a one-shot gold flash overlay the frame the rank lands. Auto- dismisses after 8 seconds; hover or focus pauses the timer; Esc closes immediately. Anonymous players see a "Sign in to claim this rank" CTA instead — the run is cached locally and back-ranked at first login, so nothing is lost.

And the Daily Challenge — a deterministic seed derived from today's UTC date. Click "Play Today's Daily" on the leaderboard and you'll start a run with the same dungeon every other player gets today. Toggle "Daily Only" to scope the leaderboard to runs that used today's seed. Resets at UTC midnight; live countdown on the tile.

What landed

  • /leaderboard page — 11 categories × 4 class filters × 4 time buckets × hardcore on/off. Sticky filter bar, podium top-3 with tier-tinted glow, table 4–50 with zebra rows + class accents
    • "YOU" badge for your row. Public — no login required to browse.
  • Profile rank widget at /u/<slug> — top 3 best ranks card grid between Featured and Stats. Class-accent borders, tier dots (legendary / epic / rare / uncommon / common), placeholder cards fill empty slots, full-width empty state with /game CTA when no ranks claimed.
  • End-of-run rank reveal modal — slides in over the death / victory screen, summary stats + tier-tinted rank reveal + Beats X% subtitle. Auto-dismiss in 8s, Esc closes, hover pauses.
  • Daily Challenge — deterministic seed-of-the-day, "Play Today's Daily" CTA, "Daily Only" filter, live UTC-midnight countdown. Same dungeon for every player, every day.
  • Anonymous run capture — runs played without login still get cached locally and submit on next sign-in, so anyone who plays before signing up doesn't lose their first wall-plant.
  • Privacy mask on rankings — players with profile visibility off appear as (private) in the leaderboard. Their rank still counts; only the link to /u/<slug> is suppressed.
v0.36.0#April 26, 2026Feature

Your Name on the Wall

We've spent a sprint giving every player a public address. You can now claim a username, point people at aot-game.com/u/<you>, and the page that loads will pull together everything you've earned — your active run, your fallen hardcore characters, your trophy shelf, your class breakdown.

Claim happens in Settings → Profile. Pick a name (3–20 chars, letters / digits / underscore), watch it light up green when it's free, hit Claim. You can rename later — the URL changes immediately, the old slug becomes available for someone else, and that's fine. There's a switch right under it for "Show publicly" if you'd rather keep the page off the open web; flipping it off turns /u/<you> into a 404 for visitors but leaves your username in the lobby intact.

The profile page itself is built like a torch-lit memorial: header with the @username in pixel font, a Featured Character card showing your deepest live run with class-tinted accents, a stats row (total characters, deepest floor, highest level, hardcore falls), the trophy grid sorted by category with rarity-tinted borders and hover descriptions, your time-per-class breakdown if you've played more than one, and finally a Hall of the Fallen collecting your hardcore deaths as cards — names, levels, deepest floor, in death-order. Hardcore deaths are wins on this page; that's the deal.

Discovery loops back through the multiplayer browser. Every host nick in the lobby with a public profile is now clickable — opens the trophy hall in a new tab so you don't lose your place in the room list. Anonymous hosts and private profiles render as plain text, same as before.

What landed

  • Public profile pages at /u/<username> — server-rendered, pulls aggregate stats + achievement unlocks across all your save slots, plus a Hall of the Fallen for past hardcore characters.
  • Username claim flow in Settings — live availability check (300ms debounced), bilingual EN+PL validation messages, optimistic visibility toggle. Rename allowed (URL changes; old slug freed).
  • Lobby linking — host nicks in the room browser link to the host's public profile when they have one. Privacy switch on the host side hides the link cleanly.
  • Reserved usernames — system routes (admin, api, dashboard), brand words (adventure, trials, aot), and a small set of generic noise (null, system, support, etc.) won't claim. Class names are fair game — Warrior_42 is yours if you want it.
v0.35.0#April 26, 2026Feature

Mouse Is Now Optional

The previous patch landed heavy attacks on hold-LMB only — which quietly meant Sprint 33's whole keyboard-first push fell apart the moment you wanted to charge a swing. Mouse mandatory, just for the heavy. Fixed.

Hold Space for heavy melee. Hold F for charged ranged shots. The 200ms threshold cleanly separates a tap (light attack) from a hold (class heavy — warrior cleave / archer charged shot with a 2-enemy pierce / mage channeled bolt with 40px AoE). Mouse LMB still works for players who prefer it; nobody loses anything.

We also caught up the onboarding so the new verbs are actually discoverable. The chrome footer under the canvas now shows Shift / F / G alongside the WASD / Space row it had since v0.30. The first-run tutorial got the same treatment — the cheat-sheet modal lists Shift (dash / sprint), G (class ability), and Space / F now read "hold = heavy" inline. Existing players will see the updated tutorial again on next session, since we bumped the "already seen" gate.

And one corner-case fix: the camera used to clamp flat against the map edge, which meant if you walked into the top-left corner of a floor your character ended up directly under the HP / stamina HUD cluster. The camera now keeps panning a little past the map rect when you reach an edge, exposing the dungeon-black void where the HUD lives instead of hiding tiles you actually need to see.

What landed

  • Hold-Space / Hold-F for heavy attacks — 200ms threshold, same dispatch as hold-LMB (per-class heavy variant). Tap behaviour unchanged: Space still fires the regular swing, F still fires the regular shot.
  • Tutorial cheat-sheet refreshed — added Shift, G rows; folded the heavy-attack hint into the Space / F labels. Tutorial gate bumped so existing players see the new sheet on next login.
  • Chrome footer keys updated — added Shift, F, G to the desktop keybind row. Inventory pill dropped from the row to keep it from spilling on narrower windows (Inventory still in the in-game button cluster).
  • HUD-safe camera padding — camera bounds extended 6 tiles beyond the map rect on every side. Walking into a corner no longer parks tiles behind the HP / stamina overlay. Physics world stays at the real map size — entities can't actually leave the playable area.
v0.34.0#April 25, 2026Feature

Now It Looks Like You Mean It

Last patch gave you dash, sprint, heavy attack, and a stamina bar. This patch finally makes them look like the things they are.

Sprint used to mean "the walk anim, but faster." It now plays a dedicated 12fps run cycle per class — bigger stride apex, real forward lean, an air-frame where both feet are off the ground, plus the kind of class flair the rest of the game has been carrying: warrior dust puffs at the rear foot, archer hood flick + quiver bounce, mage robe billow with a violet glimmer trailing the back foot. You'll see the difference the moment you hold Shift.

Dash and heavy attack got the same treatment. Each class sheet grew eight new frames per direction: three for the dash burst, two for the heavy wind-up loop, three for the release follow-through. Warrior shoulders into the dash with sparks. Archer's body shifts opposite to the bow, hood trails behind. Mage briefly dissolves into a dithered ghost of itself before re-forming. The heavy back-cock visibly pulses while you hold LMB — release fires the swing / loose / bolt as a real animation, not the regular attack swap-out.

The stamina bar also stopped being inert. It now reads what it's actually doing — a soft 1.5s shimmer band sweeps when you're refilling, a backward wind-flicker streams past the trailing edge while sprinting, the whole bar warm-pulses to amber under 25%, and hitting zero flashes the frame red for 400ms before settling into a striped lockout pattern. A boot sigil on the left, a dash-ready pip on the right, and tick marks at 25 / 50 / 75% reveal as the fill recedes past them.

A few mechanical follow-ups landed quietly alongside the art:

What landed

  • Run cycle sprites — three new sheet_sprint.png files (one per class), 4 directions × 4 frames at 12fps. Walk stays at 8fps for non-sprint movement.
  • Dash + heavy attack sprites — base sheets extended from 320×128 to 576×128. Cols 0–9 unchanged (idle / walk / attack / hurt); cols 10–17 add dash burst (10–12), heavy wind-up loop (13–14), heavy release (15–17).
  • Stamina HUD polish — boot sigil, dash-ready pip, threshold ticks, and four animated states (refill shimmer, sprint flicker, low pulse, exhausted lockout) all driven from live values, no per-frame JS.
  • Dash on Shift release, not press — first version dashed the moment you tapped Shift, which meant every "I want to sprint" burned 35 stamina on an unwanted dash. Now: tap (under 200ms) = dash; hold past 200ms = sprint engages. Quick double-tap still chains two dashes.
  • Sprint actually drains stamina now — the throttle gate on the stamina setState was 0.5, and per-frame sprint drain at 60fps is 0.42. Every frame of drain was getting filtered out, so sprint visually engaged but the bar never moved. Drop the gate, sprint drains as designed.
  • Closed doors finally stop arrows — the projectile-vs-door check was checking the wrong grid (pathfinding 0/1 instead of the TILE enum), so doors never registered as solid. Walls were fine by coincidence (TILE.WALL === 1). Read door state from the actual sprite map. As class flavour: archer's volley sails through closed doors, normal arrows don't.
  • Hydration error squashed — mute button + stamina bar were both reading state set from localStorage / performance.now() on first render, which mismatched server vs client.
v0.33.0#April 25, 2026Feature

Dodge, Run, Hit Harder

Combat used to have one verb: attack. You walked toward the thing, you hit the thing, you walked away. We've spent this patch giving the player more verbs.

Shift is the big one. Tap it and you dash — a quick burst in the direction you're moving, with i-frames so the slime that was about to land a hit ends up biting air. Hold Shift and you sprint at 1.5× speed. Both pull from a new stamina bar that sits between HP and XP, refills while you stand still, refills slower while you walk, and empties faster than you'd like when you panic-spam Shift in a corner.

Each class flavours their dash differently. Warrior charges — 50% weapon damage on the first thing your shoulder meets, plus a stagger they need a moment to recover from. Archer backsteps — always opposite the way you're facing, with a +30% range buff on the next arrow so you can keep your distance. Mage blinks — longer, faster, hungrier in stamina cost. Worth it.

Hold left-click for a heavy attack. Warriors cleave a 90° arc and hit up to three enemies at once. Archers charge a shot that does double damage and punches through two enemies in a line. Mages channel a bolt that detonates with a small splash on impact. The trade is movement — while you're charging, you're at 60% speed, and anyone with a ranged attack will use that against you.

And finally, the mouse is now optional. Movement-based facing was forcing keyboard players to think about cursor position they couldn't see. Now offensive actions auto-target the nearest enemy in range unless you've moved the mouse in the last second — meaning a keyboard-only run finally plays like a keyboard-only run instead of "WASD plus invisible aim".

What landed

  • Dash + sprint on Shift — tap dashes 96px (3 tiles) in the movement direction with i-frames; hold sprints at 1.5× base speed.
  • Stamina bar — green slim bar between HP and XP. 100 cap, 30/s regen while moving, 60/s standing still, 25/s drain while sprinting.
  • Class-flavored dashes — Warrior shoulder charge (+50% ATK on contact + stagger), Archer backstep (always opposite of facing
    • 0.6s focus window for +30% arrow range), Mage blink (4 tiles in the same window, costs 1.5× stamina).
  • Heavy attack on hold-LMB — 400ms charge, 1.5s cooldown, 60% movement speed during wind-up. Class variants:
    • Warrior cleave — 90° arc, hits up to 3 enemies, 1.5× ATK.
    • Archer charged shot — 2× ATK, pierces 2 enemies in line, longer range.
    • Mage channeled bolt — 1.8× ATK, 40px AoE on impact.
  • Aim-decoupled facing — sprite faces cursor (or auto-target) for 500ms after each offensive action, so you can WASD-backpedal and keep arrows aimed at the enemy chasing you.
  • Auto-target nearest enemy — keyboard-only players: SPACE for melee, F for ranged, G for ability now all dispatch toward the nearest enemy in 250px without needing the cursor. Mouse still wins when you've moved it in the last second.
  • Character sheet tabs — Stats / Skill Tree / Trophies split into three tabs (1/2/3 to switch, Tab cycles, Esc closes). Skill tree finally has the modal width to itself.
v0.32.1#April 25, 2026Hotfix

The Shopkeeper Remembers He Has a Shop

It turns out the shopkeeper was only really committed to the first shop he opened. After that, the dungeon kept generating his shop room — counter, rug, shelves, the whole setup — but the man himself wasn't there. Walk back to a floor where he greeted you ten minutes ago? Empty. Descend to a brand new shop floor? Also empty.

Fixed. The spawn was tied to the enemy spawn pass, which gets skipped when you re-enter a floor you've already cleared. So the shop room loaded, the props loaded, and then the part that places the shopkeeper quietly didn't run. He now spawns on every entry to a shop floor — first visit, tenth visit, doesn't matter.

What landed

  • Shopkeeper present on every shop floor — both first-time visits to new shop floors and revisits to cleared shop floors. He's also back where you left him on a floor you already cleared.
▼ Older dispatches · Page 10