For a while now the bots have been swinging at thin air, swords pointed roughly at their own kneecaps. As of this release they’ve remembered which way they’re facing, so fights with bot-controlled units are once again fights rather than interpretive dance.

Everything else is plumbing. Crash reporting, the auto-updater, and a few UI widgets have moved into the studio’s shared library, ready to be picked up by the next game without copy-paste. Nothing you’d notice from inside the tavern; just a tidier shed out back.

  • GHA release + local build.ps1: optional build input (0-99) trails the Android versionCode (new formula MAJ*1_000_000 + MIN*10_000 + PAT*100 + BUILD), so a failed Play upload retries with a bumped suffix instead of burning a patch bump on the public version string. 1.4.6 build 0 lands at versionCode 1040600; the old scheme’s 10406 stays strictly smaller, so monotonicity holds across the cutover. Closes #9.
  • Bots now swing in the direction they’re facing. The server-side bot loop was passing the bot’s own position as the attack aim, so aim - playerPos collapsed to (0, 0) and the melee hit cone had no orientation; every swing whiffed and [ATTACK] facing=(0.0,0.0) filled the sim log. Aim is now bot.Pos + bot.Facing * 40, matching the convention real clients already use. Fixes #8.
  • Client.Core/UI/Button.cs gains a LabelScale opt-in property (defaults to 1.0 = current desktop behaviour, no change today). Mirrors the same shape Fusion picked up from core 1.2.0’s Resinhead.Shared.Ui.UiMetrics.Scale(viewportHeight); the day Blight wants to scale labels for a phone or 4K target, the property is wired and callers can pair it with UiMetrics.Scale. The two ad-hoc uiScale = sh / 200f calls in Game1.cs (GAME OVER / YOU DIED overlays) deliberately untouched: they’re stylistic font scaling at a different baseline, not the touch-target problem UiMetrics solves.
  • Velopack auto-updater moves to core 1.1.0. Local Client.Desktop/AutoUpdater.cs deleted; Game1’s desktop-only path constructs Resinhead.Shared.Updates.AutoUpdater(s3Url) instead. Per-arch channel routing (osx-arm64 / osx-x64), no-op-when-not-Velopack-installed, and the UpdateState lifecycle all live in the package now; Blight’s update-feed base URL is the only game-specific input. Explicit Velopack 0.* reference dropped from Client.Desktop.csproj; reaches Blight transitively via Resinhead.Shared from now on. Behaviour unchanged at the user-facing level (same feed, same download flow, same restart).
  • Resinhead.Shared bumped to 1.0.0: the floating 0.0.*-* prerelease pin in Shared/Shared.csproj swaps for a stable 1.* range now that core has committed to API stability (strict semver honoured from 1.0 onwards). Local Shared/SentryBootstrap.cs becomes a thin per-game wrapper; the studio-neutral plumbing (DSN resolution, no-PII defaults, env-var scrubbing, session-tracking gating, environment detection, app / rid tags) lives in Resinhead.Shared.Telemetry.SentryBootstrap and is parameterised by TelemetryOptions (game id, app kind, version, player-facing flag, opt-out hook, breadcrumb scrubber callback). BlightSettings.SendCrashReports and the chat / lua.source breadcrumb scrubbing stay Blight-side as the wrapper’s option values; CaptureLuaError stays Blight-side because Lua is Blight’s scripting layer, not a studio concern. Behaviour at the Sentry dashboard is unchanged (same DSN, same release tag blight@$VERSION, same scrubbing posture, same session-tracking shape); just relocated. Explicit Sentry 5.* reference dropped; Resinhead.Shared brings Sentry transitively (currently 6.x) so Blight inherits the studio’s chosen Sentry version going forward.
  • Resinhead.Shared wiring: nuget.config at the repo root declares the org’s private GitHub Packages feed; the build and build-android jobs gain permissions: packages: read and pipe the runner’s GITHUB_TOKEN straight into NuGet via NUGET_USERNAME/NUGET_TOKEN env vars. Cross-repo auth works because resinhead-games/core granted this repo Read access on each Resinhead.* package’s settings page. (Initial attempt used a resinhead-games-packages GitHub App + actions/create-github-app-token; the mint succeeds but the GitHub Packages NuGet download endpoint silently 403s App installation tokens despite REST API support. Same-org GITHUB_TOKEN is cleaner anyway: zero secrets to rotate. Postmortem in core/docs/nuget.md.)
  • Resinhead.Shared 0.0.1 referenced from Shared/Shared.csproj as a floating prerelease range (0.0.*-*) so a local dotnet pack of core into ~/.nuget-resinhead-local/ automatically shadows the published stable while iterating; see README.md and core/docs/nuget.md. Package is empty today (one internal placeholder class), so this is the proving run for the auth + restore pipeline rather than any actual code reuse; the moment something interesting wants to live in two games, that’s where it goes.
  • GHA release: per-locale Google Play notes now come from docs/release/$VERSION.md instead of being pasted into the Play Console by hand. New validate job parses the file (50 locales as XML-tag blocks) and fails the pipeline early on empty/oversized bodies, duplicates, or known-bad tags (id-ID, he-IL). The distribute-googleplay job then injects the parsed releaseNotes array into the track-update PUT, so notes attached at internal-upload time pre-fill the Console’s promote-to-prod dialog. 1.4.4 and 1.4.5 notes archived under docs/release/ as the parser fixtures, and 1.4.6 ships with an empty-body skeleton so the validator hard-fails until the cycle’s notes are actually written.
  • Sentry Release Health: turn on AutoSessionTracking for Client.Desktop and Client.Android so each launch posts a session ping (release + environment + status; no payload data, scrubbing posture unchanged). Sentry now fills in crash-free-session rate, crash-free-user rate, and per-release adoption alongside the raw error feed. Gated to player-facing clients only; Facilitator (long-lived server, no meaningful “session”) and BotClient (CI tooling) stay off.