Mudbury stops feeling like a ghost town. The tavern has patrons and a few heroes nursing their drinks, the pumpkin patches finally read as kitchen gardens rather than cairns, and a swarm of invisible walls that had been quietly forcing your character to take the scenic route are gone; click on open ground and you actually walk to it. Behind the scenes, a new opt-in dev console lets our tooling inspect a running game, and the studio’s C# and markdown linting bars now run on every change.

  • Tooling: studio-wide markdown lint via markdownlint-cli2-action and a strict .markdownlint.jsonc (default rules on, MD013 bumped to 120 with tables and code blocks exempt). New markdown-lint.yml workflow runs on every PR and main push. Existing docs reflowed to fit. Per-locale Play Console release notes moved from docs/release/*.md to docs/release/*.txt since they’re machine-parsed XML-fragment input rather than markdown prose; release pipeline updated to match. First repo in the cross-org rollout tracked by resinhead-games/infra#29.
  • Build: dropped the paths: filter from markdown-lint.yml. markdownlint is a required check, so skipping it on non-markdown PRs left the gate stuck “expected” and blocked every such merge. It now runs on every PR. Closes #83.
  • Deps: shared core packages pinned to 1.5.1 (Resinhead.Mcp, Resinhead.Shared, Resinhead.Analysers); the bump carries Velopack 1.0.1 into the auto-updater. Two were floating 1.*, now explicit, so each release records exactly which core it built against.
  • Dev: opt-in MCP server embedded in Debug Client.Desktop builds. Pass --mcp-port <N> and Blight stands up a Streamable HTTP Model Context Protocol endpoint on 127.0.0.1:N, gated by a per-process bearer token written to %TEMP%/resinhead-mcp-<pid>.token, so Claude (or any MCP client) can introspect the running game without writing one-off Lua survey scripts each time. Tool surface starts deliberately tiny (echo, window_title) to prove the wiring round-trip; lua_eval, teleport, encounter dumps and screenshots land in follow-ups once a main-thread dispatcher is in place. Wired via Resinhead.Mcp 1.5.0 from core; the package reference and the implementing files are Debug-conditional + #if DEBUG wrapped (belt-and-braces), so Release builds carry zero MCP code or dependencies. Set RESINHEAD_MCP_TOKEN in your environment for a stable bearer token across restarts; otherwise each launch rotates a fresh random one in the per-pid token file. Closes #75.
  • Build: the lint sweep in #65 stripped four using directives whose only consumers lived inside #if ANDROID / #if !ANDROID blocks the formatter never expanded, so they looked unused. Result: Android and Desktop builds went red on main and two release runs failed on 2026-05-12 before anyone noticed. Each using restored (System.Diagnostics, Microsoft.Xna.Framework, Microsoft.Xna.Framework.Input.Touch, System) wrapped in the matching #if, mirroring the existing Resinhead.Shared.Updates pattern in Game1.cs, so the next format pass can’t strip them again. Closes #70.

  • Tooling: studio-wide C# lint bar via Resinhead.Analysers NuGet (auto-imported through Directory.Build.props); new lint.yml runs dotnet format --verify-no-changes per-project on PR + push. Per-project (not solution-level) because the formatter chokes on Blight’s linked source files at solution level. .gitattributes locks LF on source + config so format passes on both Windows and Linux. Existing C# reformatted (braces, line endings). Closes #64.
  • Pathing: the south side of Mudbury’s tavern had a campfire with a phantom 30x30 wall hiding behind it. A* was dutifully routing around the invisible obstacle whenever a click landed past the campfire, so the character would amble the long way counterclockwise around what looked like open ground. Wall deleted, campfire becomes the pass-through firepit it pretends to be. Progress on #12.
  • Pathing: four more invisible wall segments in Mudbury deleted, no replacement sprite ever existed. Two south-road “fence” lines at cy+400 and a pair of “market stall” verticals east of the tavern, all of them solid in the sim but blank on the screen. Same flavour as the well, same fix; A* no longer has secrets the player can’t see.
  • Mudbury pumpkins: three patches re-laid as loose kitchen gardens rather than tight piles. 16 pumpkins down to 10, jitter boxes widened roughly 4x so neighbours don’t visually merge, scale floor dropped from 2.0 to 1.4 so size varies more, and the cottage garden nudged east off the dirt exit path it was sitting on top of. Closes #12.
  • Dev: new collision_debug([on]) Lua global overlays every WallSegment as translucent red, with the +22f pathfinder buffer shown as a faint yellow halo, so visual triage of “what’s actually solid here” is one console line away. Two new survey scripts (scripts/tests/pumpkin_survey.lua, scripts/tests/collision_walkabout.lua) bundled as demonstrations of the pattern.
  • GHA release: bump aws-actions/configure-aws-credentials@v5 -> v6 and actions/download-artifact@v7 -> v8 (24 spots) to clear the Node 20 deprecation annotation. Both new majors are Node 24 native, so the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env workaround at the top of the workflow goes with them; any future Node 20 straggler now surfaces as a warning rather than being silently masked. Closes #49.
  • Tooling: .github/dependabot.yml added; daily auto-PRs for github-actions, nuget, and the Facilitator Dockerfile base image. Same shape mirrored across the rest of the Resinhead fleet (fusion, core, infra, website, game-template) and the cool.au Jekyll sites (bundler only) so the next Node 20-style deprecation arrives as a PR instead of a five-repo cleanup.
  • GHA release: bump azure/login@v2 -> @v3 (both Azure-signing jobs, 2 spots). Pure Node 24 runtime upgrade per the v3.0.0 release notes; no input changes. Manual sweep ahead of Dependabot’s first daily pass.
  • Mudbury tavern: bar counter pulled 30 units south of the back wall so the room finally has a barman-sized gap behind it; the back-bar barrels and crates moved with it, regrouped at the west and east ends sitting on the bar’s back edge with a clear centre for whoever ends up tending. Added four stools along the south face so patrons can pull up a seat. The “E back, near bar” table got removed; with the bar moved south there was no longer clearance for it without crowding Table 3. Progress on #31.
  • New survey script scripts/tests/tavern_bar_survey.lua for the same triage pattern as pumpkin: tp + zoom + screenshot the tavern interior from six angles in one run.
  • Tavern bar: two stubby wooden “boards” lying on the floor next to the bar were collision walls poking out 40 east and 25 south of where the bar sprite actually drew. Wall arcs render as visible wood quads via DrawWallQuad, and the bar sprite was only covering part of its own collision; the rest of the arc printed onto the floor as a stray. Collision rectangle trimmed to match the sprite footprint (and the now-redundant “left leg” wall deleted since the east end was always open anyway). Closes #31.
  • Rendering: Y-sort for oblique scenery now matches the sprite’s anchor. The world-pass sort key used h * 0.5 (geometric centre of the sprite) while the renderer anchors sprites at h * 0.85 (a fair way down), so wide props like the bar counter reported their “feet” line nearly half a sprite north of where they actually stood. Players standing visibly in front of the bar got painted over by it. Sort key switched to h * 0.15, mirroring 1 - 0.85, so feet line up. Closes #62.
  • Tavern tables: every round table had a small dark horizontal strip showing through it east-west, the table’s own collision wall fill rendering through the rounded sprite’s transparent rim. Same shape of bug as the bar stubs; this time deleted the collision walls outright. Tables become decorative-only; chairs and stools around them still keep players from cutting straight through the seating area. Architectural follow-up tracked in #63 so future scenery can have invisible collision.
  • Quest waypoint arrow: the gold “go here next” arrow was hard-coded at 18 pixels regardless of resolution, which on a desktop monitor put it somewhere between unobtrusive and invisible. Size now scales with viewport height (roughly 4.5% of screen height, clamped 28-80px), the screen-edge margin scales with it, and a thin black outline sits behind the gold fill so it still reads over light tiles. Mobile gets the same treatment for free. Closes #59.
  • Mudbury tavern, populated: the room now has people in it. The three storytellers (Elder Grumbald, Mabel, Farmer Giles) moved out of their original “stand wherever” spots onto south-of-table positions where the table reads behind them, and six new background patrons fill out the rest of the room: Gerald the barkeep behind the bar, Tobias Stout and Wilma Tipps at table 5, Hops Cooper at table 6, Pog and Cheryl Mudge on bar stools. Mug sprites bumped from scale 1.1 to 4.0 (they were rendering smaller than a pixel of paint), candles to 2.0, and every “on-the-table” prop nudged south of its table centre so the Y-sort actually puts it on top. Bar counter shrunk from scale 4.5 to 3.0 so Gerald isn’t an occluded forehead. Mix of villager, merchant, and soldier palettes so the room reads as a crowd of different people rather than five identical brown tunics. Closes #30.
  • Quest mode: encounters can now declare a party_spawn table of {x, y} seats. The player takes index 0, bots take 1..N, and bots stay Frozen through the intro dialogue so they don’t drift before combat starts. Wired through EncounterDef.PartySpawn, parsed by QuestLuaApi, used by GameSimulation.AddPlayer / SpawnBots, with UnfreezeAllBots() fired by QuestModeHandler when the intro ends. First user is Mudbury tavern: the heroes spawn sat at table 2 together as friends having a drink, rather than dumped on top of each other at the scene centre.
  • Host spawn race: CreateLocalPlayer() was being called after StartHost() in all three host paths (--host CLI, --auto-start CLI, and the UI “Start Hosting” button), so the server’s PlayerRespawned broadcast (fired inside AddPlayer) arrived while _localPlayer == null and the position got dropped on the floor. ClientCreature.CreatePlayer then defaulted to (WorldSize/2, WorldSize/2), leaving the client player a full world-quadrant away from where the server thinks they are. UI and auto-start paths had a hardcoded _localPlayer.Position = SceneCenters[0] papering over it, which also silently stomped quest-mode party_spawn overrides. All three paths now create the local player before StartHost() and let the server be authoritative.
  • New Lua global: cam(x, y) snaps the camera without moving the player, so screenshot-survey scripts can frame a scene without dragging the protagonist around the room. tp(x, y) still does the old behaviour (move both). scripts/defs/blight.lua and the in-game ? help text updated. New scripts/tests/tavern_villagers_survey.lua uses the new API.
  • Quest spawning: enemy waves used to materialise on a near-perfect ring around the party (random angle, but distance always R..2R), which read as “the encounter has started” more than as a fight breaking out. Each enemy group now picks its own ~90 degree arc and a wider radial range (0.4R..1.5R), so scarecrows might come in from the north while crows come in from the east, and not every enemy stands the same distance away. Encounter 2 stops looking like a circle drawn for you. Closes #35.
  • GHA release: distribute-googleplay swaps the long-lived GOOGLE_PLAY_SERVICE_ACCOUNT_JSON key for Workload Identity Federation. New blight-877312 GCP project hosts the pool + provider + blight-play-publisher SA, scoped via principalSet to resinhead-games/blight only. Job gains id-token: write; setup-gcloud step gone; gcloud auth print-access-token --scopes=... line gone (it silently ignored --scopes for external_account credentials, the trap fusion@7d0bdc4 hit). Token now arrives pre-scoped from the auth action and feeds the curl calls directly. Closes #48.
  • Crashes: Android native stack frames now resolve to readable function names in Sentry and the Play Console. The release pipeline zips the unstripped .dbg.so companions out of app_shared_libraries, pushes them to Play’s deobfuscationFiles/nativeCode endpoint after the bundle upload, and the existing Sentry debug-files upload walk picks them up transparently. Covers app + AOT’d assembly-store frames; Mono runtime .sos ship from the .NET workload pack and aren’t included here. Closes #7.