Mudbury Comes to Life
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-actionand a strict.markdownlint.jsonc(default rules on, MD013 bumped to 120 with tables and code blocks exempt). Newmarkdown-lint.ymlworkflow runs on every PR and main push. Existing docs reflowed to fit. Per-locale Play Console release notes moved fromdocs/release/*.mdtodocs/release/*.txtsince 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 frommarkdown-lint.yml.markdownlintis 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
corepackages pinned to 1.5.1 (Resinhead.Mcp,Resinhead.Shared,Resinhead.Analysers); the bump carries Velopack 1.0.1 into the auto-updater. Two were floating1.*, now explicit, so each release records exactly whichcoreit 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 on127.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 viaResinhead.Mcp1.5.0 fromcore; the package reference and the implementing files are Debug-conditional +#if DEBUGwrapped (belt-and-braces), so Release builds carry zero MCP code or dependencies. SetRESINHEAD_MCP_TOKENin 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
usingdirectives whose only consumers lived inside#if ANDROID/#if !ANDROIDblocks 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 existingResinhead.Shared.Updatespattern inGame1.cs, so the next format pass can’t strip them again. Closes #70. - Tooling: studio-wide C# lint bar via
Resinhead.AnalysersNuGet (auto-imported throughDirectory.Build.props); newlint.ymlrunsdotnet format --verify-no-changesper-project on PR + push. Per-project (not solution-level) because the formatter chokes on Blight’s linked source files at solution level..gitattributeslocks 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+400and 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.0to1.4so 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 everyWallSegmentas translucent red, with the+22fpathfinder 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->v6andactions/download-artifact@v7->v8(24 spots) to clear the Node 20 deprecation annotation. Both new majors are Node 24 native, so theFORCE_JAVASCRIPT_ACTIONS_TO_NODE24env 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.ymladded; daily auto-PRs forgithub-actions,nuget, and the FacilitatorDockerfilebase image. Same shape mirrored across the rest of the Resinhead fleet (fusion, core, infra, website, game-template) and the cool.au Jekyll sites (bundleronly) 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.luafor 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 ath * 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 toh * 0.15, mirroring1 - 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, andsoldierpalettes 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_spawntable of{x, y}seats. The player takes index 0, bots take 1..N, and bots stayFrozenthrough the intro dialogue so they don’t drift before combat starts. Wired throughEncounterDef.PartySpawn, parsed byQuestLuaApi, used byGameSimulation.AddPlayer/SpawnBots, withUnfreezeAllBots()fired byQuestModeHandlerwhen 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 afterStartHost()in all three host paths (--hostCLI,--auto-startCLI, and the UI “Start Hosting” button), so the server’sPlayerRespawnedbroadcast (fired insideAddPlayer) arrived while_localPlayer == nulland the position got dropped on the floor.ClientCreature.CreatePlayerthen 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-modeparty_spawnoverrides. All three paths now create the local player beforeStartHost()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.luaand the in-game?help text updated. Newscripts/tests/tavern_villagers_survey.luauses 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-googleplayswaps the long-livedGOOGLE_PLAY_SERVICE_ACCOUNT_JSONkey for Workload Identity Federation. Newblight-877312GCP project hosts the pool + provider +blight-play-publisherSA, scoped via principalSet toresinhead-games/blightonly. Job gainsid-token: write;setup-gcloudstep gone;gcloud auth print-access-token --scopes=...line gone (it silently ignored--scopesfor 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.socompanions out ofapp_shared_libraries, pushes them to Play’sdeobfuscationFiles/nativeCodeendpoint after the bundle upload, and the existing Sentrydebug-files uploadwalk 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.