github binary-knight/usurper-reborn v0.65.1
Beta v0.65.1 Release Notes

latest releases: v1.0.3, v1.0.2, v1.0.1...
one month ago

Release Notes - v0.65.1 (Countdown)

Second release of the Beta -> 1.0 "Countdown" cycle. v0.65.0 shipped the onboarding package (B1), bounded late-game monster accuracy (B2), the Linux auto-updater fix (B3), launch-hygiene items (B5), and a batch of player-reported fixes, all deployed via CI. v0.65.1 continues the 1.0 run-up: telemetry-gated tuning now that the v0.65.0 funnel + accuracy data has accumulated, plus any new player-reported fixes.

Onboarding funnel: connection-type attribution fixed (B1a follow-up)

The v0.65.0 funnel told the headline story it was built to tell: across the first eleven days, of the new-account cohort 75% created a character, 70% reached town and actually started playing, 50% got a first kill, and 30% logged in again on a later day. That is a different game from the historical 87% zero-minute bounce that made onboarding the #1 launch blocker; the Quick Start + Journal-ticker package moved the needle hard. Bounded monster accuracy (B2) also reads as working: solo fights on floors 30-39 now cost ~98% of the player's HP over ~8 rounds where they used to cost ~0 (monsters could only land natural 20s). Late-game tension is restored; the >=30 sample is still tiny (single-digit fights), so no further tuning this release -- the clamp is doing its job and over-tuning on five fights would be reckless.

The one real gap the data exposed: every funnel milestone records its connection type (Local / MUD / Steam) EXCEPT account_created, which was always blank -- so the Web-drive-by vs Steam-buyer split the funnel was explicitly built to show could not be drawn. Root cause: account_created fires inside RegisterPlayer, which runs during the auth handshake BEFORE PlayerSession builds SessionContext.Current, so the SessionContext.Current?.ConnectionType read at the write site was null. Fix: RecordOnboardingEvent now self-heals -- when any later milestone arrives carrying a real connection type, it backfills the still-blank account_created row for that user. For a new account the next milestone (character creation, reaching town) is the same session, so the attribution is correct; no call-site changes, no dependency on auth-time plumbing, and existing blank rows fill in if those users trigger any further event. Confined to one method, best-effort try/catch like the rest of the funnel writes.

Heal-target menu shows ally HP% on the ability path too

Player feedback: the in-combat heal-target picker for a SPELL heal lists each ally's HP percentage (handy for an at-a-glance read of who needs it most), but the picker for a STAMINA/ability heal (the CanTargetAlly path, e.g. an Alchemist's Healing Elixir or a healer NPC's curative) showed only raw current/max HP, no percent. The two menus had drifted. The ability-path loop already computed each candidate's HP fraction (it uses it for the red/yellow/green coloring) -- it just never printed it. Fix: the combat.heal_target_option line now appends the percentage ([1] Aldric (45/120 HP, 38%)), matching the spell picker, in all 5 languages. Selection logic, target ordering, and the candidate set are unchanged; this is display-only. (Other structural differences between the two menus are intentional: the spell picker offers a Q/C/X cancel because it runs before the spell commits, while the ability picker runs mid-resolution after the cost is already paid, so cancel does not apply there.)

Weapons and shields now show their class (1H / 2H / Shield) before you equip them

Player request: it was "pretty much impossible to find out if a new weapon is 1H or 2H without just trying it on," and some off-hand items that look like shields (a buckler) weren't obviously shields. New shared helper GameConfig.GetWeaponClassTag returns a localized class tag -- One-Handed / Two-Handed / Shield / Buckler / Tower Shield / Off-Hand -- and it's wired into four display surfaces: the loot-drop screen (a Type: Two-Handed line under the dropped weapon's stats), the loot comparison screen (the currently-equipped item's class too, so you can see the tradeoff -- a dropped 2H sword vs your current 1H plus shield), and the inventory, both equipped weapon slots and unequipped backpack items (a [Two-Handed] / [Shield] bracket, mirroring the existing armor [Heavy] weight tag). Shield detection is robust: it keys off the weapon type OR the block/shield-bonus stats OR the item name, the same logic combat uses to decide whether a shield is equipped -- so a buckler that an older save mis-typed as a one-handed weapon still reads as a buckler here, which directly answers the "is this actually a shield?" confusion. Display-only; no item data, generation, or combat behavior changed. The name-based weapon-type inferer (which defaults to Sword on unknown names) is only consulted for items already known to be weapons, so armor and accessories show no tag. Build clean, 845/845, all 5 languages.

Take a family surname when you marry

Player request: at the wedding, let the player adopt their spouse's surname or pick a new family name. The marriage ceremony (Church, Love Corner, and the in-dialogue proposal) now offers three choices after the vows: keep your name, take your spouse's surname, or enter a new family name. Whatever you pick shows as your name everywhere -- status sheet, /who, the website leaderboard, NPC dialogue -- and newborns inherit it.

Design note (why it's safe): the player's display name is secretly an identity key across about seven systems (children match parents by name, plus relationships, worship, quests, kingship), so a literal rename would risk orphaning your family/quests/marriage -- the same hazard class as the permadeath cross-link bug. So the surname is implemented as a DISPLAY layer: a new Character.FamilySurname rides on DisplayName while the underlying identity key (Name2) never changes. Nothing that matches on your name is disturbed -- a child still records the stable name for parent-matching, so the family tree stays intact -- but you visibly become "Hera Ashwick". Newborn inheritance is likewise display-only: the child's stored parent fields keep the stable name; only the child's shown name gets the surname appended. The DB display_name column (leaderboard / character select) and the live /who list are both composed with the surname so every surface agrees; entered surnames are sanitized (letters/space/hyphen/apostrophe, capped at 20). A save-state-reviewer pass confirmed the round-trip is complete on single-player and online, pre-0.65.1 saves default to no surname, and the identity key is genuinely untouched. Deliberately NOT a true rename (that deeper option was offered and declined as too risky for 1.0 prep).

Guild Officers can withdraw far more gold (50k -> 10m per transaction)

Player report: the guild bank's Officer gold-withdrawal cap of 50,000 per transaction was painfully low at endgame, where the common pattern is to "dump" a large stash into the bank before a risky fight and reclaim it after respawning. With the 50k cap, only the Leader (unlimited) could pull a big stash back, so reclaiming effectively required the Leader to be online. Raised GuildSystem.OfficerGoldWithdrawLimit from 50,000 to 10,000,000, which the player suggested. This is a single per-transaction limit (there is no daily cap), and the real guard against over-withdrawal is unchanged: the bank-balance check refuses any withdrawal larger than what the guild actually holds, via an atomic bank_gold >= amount update. Officers are Leader-appointed, so the per-transaction number was only ever fat-finger friction rather than a theft control; raising it lets a trusted Officer reclaim a dumped stash without needing the Leader. Members (no rank) still cannot withdraw at all. No save or schema change; the "Officers can withdraw up to N" message reads the constant, so it now shows the new figure.

Set combat skills for team NPCs, spouses, and echoes (from the dungeon menus)

Player request: the Inn lets you turn off a companion's "trash" moves so they stop wasting turns; extend that to the OTHER kinds of dungeon teammate -- recruited Team Corner NPCs, a spouse you bring along, and recruited player echoes -- reachable from the Dungeon Entrance [T] party menu AND the in-dungeon [Y] party menu. Both, as asked.

Until now the per-skill toggle only worked for the five named companions: combat filtered Companion.DisabledAbilities/DisabledSpells (gated on IsCompanion), and everyone else only got coarse role/specialization filtering. Now there's a per-teammate skill editor on both dungeon menus ([S] Set combat skills): pick a member, see their abilities and spells with ON/OFF flags, toggle the ones you don't want them using, [A] to re-enable all. The teammate's AI then skips disabled skills and falls back to its remaining skills or a basic attack (disabling everything degrades cleanly to basic attacks -- no skipped turns).

Design: companions keep their existing store (edited at the Inn or now the dungeon, same object). For non-companion teammates the toggles live on YOUR character, keyed by the teammate, in two new dictionaries (TeammateDisabledAbilities / TeammateDisabledSpells). That one player-side store covers NPCs, spouses, AND transient echoes uniformly -- no per-NPC save round-trip, no shared-world-state write, and your settings never affect another player's copy of the same teammate. Combat reads them live via the controlling player; a single AutoSave (the same call the Inn uses) persists both companion edits and the player dicts. Grouped live players are excluded -- they control their own skills. A combat-reviewer pass (parity with the companion filter, null-safety, graceful degradation) and a save-state-reviewer pass (round-trip on single-player + online, pre-0.65.1 saves default to empty = no filtering, no contamination, no bloat) both came back clean. 8 new dungeon.skills_* loc keys in 5 languages.

Group co-op: follower healers can target allies, plus a live party HP panel

Player report (online group dungeon): when a FOLLOWER (a non-leader group member) plays a healer, they had no way to choose who to heal -- heal spells always hit self; the /help keys didn't match reality (C and H "didn't work", and H used a potion); and after combat the follower's screen stayed on the combat view until they moved rooms.

Group combat is leader-driven: the leader runs the engine and followers send single-line text commands the leader's engine parses. The parser set heal spells to no target and never an ally, and -- key architectural quirk -- the leader isn't in the follower's teammate list during a follower's turn (the leader is the combat owner), so no index could even reach them. Fixes:

  • Heal/buff ally targeting (incl. the leader). New direct-target field on the combat action, set only on the follower path and checked at highest precedence in the spell-apply, so a follower can heal/buff self, the leader, or any teammate. C2T3 = cast spell 2 on target 3, where the follower sees a numbered heal-target list each turn (T1=You T2=<Leader> T3=<Ally>, with HP%). The leader's own interactive heal targeting is byte-for-byte unchanged. A follower's party-wide (multi-target) heal/buff now also reaches the leader.
  • C and H do the right thing for healers. Bare C casts the best offensive spell as before, but a pure healer (no attack spell) now casts their best heal on the most-injured party member. H casts the best heal spell on the most-injured ally (including the leader) when the follower can; only falls back to a self potion otherwise -- and that potion path now runs in quick mode so it never hangs the follower on a prompt they can't answer. (Same quick-mode fix applied to the I use-item path, which had the same latent hang when a follower carried both healing and mana potions.)
  • Post-combat screen refresh. The leader now re-pushes the room to followers on every room render -- including the redraw the instant the dungeon loop resumes after combat -- so followers are no longer stuck on the combat screen until they move. (Chosen over a follower-side flag, which wouldn't fire while the follower loop is blocked waiting for input.)
  • /help reconciled with the real keys (C/C#T#, H, heal targets).

Live party HP panel (the grouping improvement chosen this round): /party now shows the whole combat party -- the leader plus every grouped player AND the leader's NPC/companion/pet allies -- each with HP/MP and a leader/ally/you tag, so a healer always knows who needs healing (the old /party only listed human members).

A combat-reviewer pass verified the leader's own combat and single-player are untouched (the new target field is only set on the follower path; multi-target leader-inclusion is a strict no-op when the caster is the leader/solo), single-vs-multi parity, no double-apply, dead-target self-fallback, and that the follower potion paths no longer block. 11 new loc keys in 5 languages. Build clean.

Town merchant: item names and descriptions now localized

Player report (Hungarian session): the random town merchant (the wandering trader who sells healing potions, a 5-pack, an antidote, and an elixir, plus the shadier poison vial / smoke bomb / dark tonic line) showed its item names and one-line descriptions in English, even though the back-out option was already translated.

Cause: each merchant item carried its English name as both its display label AND the logic key the purchase handler switches on, and the descriptions plus the trailing " gold" price suffix were hardcoded English. Fix: the item now keeps its English name as the internal logic key but renders from new street_encounter.merchant.item.* display/description keys, and the price line is localized too. 16 new keys (8 item names, 7 descriptions, 1 price line) translated into all 5 languages. One English description that contained an em-dash was corrected to ASCII in passing. The purchase logic is unchanged -- it still matches on the English name -- so this is display-only.

Magic Shop: the "sell accessories" flow is now localized

Player report (Hungarian online session): the Magic Shop's sell prompt -- "Sell 2 items for X gold?" -- showed in English. Triage found the entire [A]ll sell branch was hardcoded English (the [F]iltered branch already routed through shop.filter_confirm), so the whole sell flow got the pass it had been missing, mirroring the Weapon and Armor shops' established keys: the Shadows fence-bonus line, the per-item "- Sell for X gold" rows, the "Sell which piece? ([A]ll, [F]iltered, 0 to cancel)" prompt, the "Sell N item(s) for X gold? (Y/N)" confirmation, and the shared filtered-sell "... and N more" overflow line. 5 new keys translated into all 5 languages (the fence-bonus line grounded on each language's already-shipped shop fence-bonus terminology; the rest mirror the weapon/armor sibling strings). The purchase logic is unchanged -- display-only.

The translated prompts deliberately keep the literal hotkeys the code matches -- [A], [F], 0, and (Y/N) -- because the Magic Shop's sell handler matches those exact letters. Where a language's word for "all" doesn't start with A (Spanish "todo", Italian "tutto", Hungarian "mind"), the prompt uses the clear label form "[A] todo" rather than relabelling the hotkey, so what's shown is always what works.

Investigating the prompt's input matching surfaced a much wider, pre-existing bug, fixed in the same release -- see the next section.

Localized "yes" now works at every confirm prompt (game-wide), and shop hotkeys match what's shown

Surfaced while fixing the Magic Shop sell prompt: a huge swath of confirm prompts displayed a localized accept key the code never accepted. Spanish and Italian show (S/N) (for "Si"), French (O/N) ("Oui"), Hungarian (I/N) ("Igen") -- but the handlers only matched the literal English Y. So a Spanish player who typed the S they were shown got nothing; they had to know to type Y. This spanned 145 Spanish / 164 Italian prompts (buy, sell, identify, reforge, enchant, divorce, execute, hire guards, repay loans, claim crown, marriage, NG+, and on and on) across the shops, bank, temple, church, castle, healer, inn, dark alley, character creation, and more.

The fix is a single shared helper, GameConfig.IsAffirmative, that recognizes a "yes" in any supported language -- Y / S / O / I plus the full words YES / SI / OUI / IGEN -- and it now backs 209 yes/no confirmations across 43 files. It is a strict superset of every check it replaced (the old == "Y", the StartsWith("Y") variants, and the few hand-rolled Y/S/O/I chains), so no existing input regresses; it just additionally honors the localized letter the player was shown. Every language keeps its natural (S/N) / (O/N) / (I/N) display. Eighteen lookalike sites were deliberately left alone after review -- menu hotkeys where S/I/O mean a different option (Skip / Inventory / etc.), and typed-word destructive confirms like "YES" / "RESET GAME" -- so nothing that depends on a distinct letter was broken. The New Game Plus prompt also had a second gate (an input-validation loop) that rejected S before the choice was even read; that now accepts the localized yes too.

Separately, four shop menu prompts had translations whose displayed hotkey letter didn't match the code's key: Spanish/Italian "sell which?" showed [T]odo/[T]utto while the code reads A; the Spanish auto-buy menu showed [S] for buy while the code uses S for skip (so typing the shown "buy" key actually skipped); and the Spanish bulk-sell-by-level/cancel options were remapped to [N]/[S] while the code reads L/Q. All corrected to the label form (e.g. [A] todo) so the shown key always works, plus an está->esta typo fixed in passing.

Remaining and flagged for a later pass: about 56 non-shop menu-navigation hotkeys (not yes/no confirms) still show a translated letter that may diverge from the code's key in some languages (es 22 / fr 15 / it 13 / hu 6). Those are a different surface (in-menu letter selection) and need a per-menu audit against each handler; the yes/no-confirm class is now fully closed.

Music Shop performances are now localized (song lyrics + Old God lore ballads)

Player report (Hungarian session): the Music Shop's performances showed in English. The menu labels were already localized, but the actual content -- the song lyrics that play when you hire a performance, and the seven Old God lore ballads -- was a set of hardcoded English text arrays. So a non-English player picked "War March", and then heard an English song.

All of it is now localized: the 12 hireable performances across the four song types (War March, Lullaby of Iron, Fortune's Tune, Battle Hymn -- a mix of comedic tavern songs and dramatic ballads) plus the 7 Old God lore songs, with their titles, intros, and every verse. 160 lines translated into all 5 languages, with the comedic songs kept funny and the lore ballads kept solemn. The {PERFORMER} name token (Melodia or Cadence) and the per-god unlock gating are unchanged; the English text still backs anything a translation is ever missing. Twenty-four em-dashes in the original English lyrics were converted to ASCII in passing (project style rule). Proper nouns (the Old God names, in-song characters like Tully and Jadu, Anchor Road) stay as-is; "the Ocean" / "the Wave" concepts are translated.

Files Changed

  • Scripts/Core/GameConfig.cs -- Version 0.65.1
  • Scripts/Systems/SqlSaveBackend.cs -- RecordOnboardingEvent backfills the blank account_created connection_type from the first later milestone (restores the funnel's Web-vs-Steam split)
  • Scripts/Systems/CombatEngine.cs -- ability heal-target picker now displays ally HP% (parity with the spell heal-target picker); RenderEquipment + ShowEquipmentComparison show the weapon-class tag (dropped item + equipped item)
  • Scripts/Core/GameConfig.cs -- new GetWeaponClassTag helper (1H/2H/Shield/Buckler/Tower Shield/Off-Hand, robust shield detection)
  • Scripts/Systems/InventorySystem.cs -- weapon-class tag on equipped weapon slots (DisplaySlot) and backpack items (DisplayBackpack)
  • Scripts/Core/Character.cs -- new FamilySurname field (display-only, rides on DisplayName; Name2 stays the identity key)
  • Scripts/Systems/SaveDataStructures.cs, Scripts/Systems/SaveSystem.cs, Scripts/Core/GameEngine.cs -- FamilySurname save round-trip (+ /who registration now uses DisplayName)
  • Scripts/Systems/SqlSaveBackend.cs -- DB display_name composed with the family surname (leaderboard / character-select)
  • Scripts/Systems/MarriageSurnameHelper.cs (new) -- ceremony surname prompt (keep / take spouse's / new), input-sanitized, live /who refresh
  • Scripts/Locations/ChurchLocation.cs, Scripts/Locations/LoveCornerLocation.cs, Scripts/Systems/VisualNovelDialogueSystem.cs -- offer the surname after the player marries an NPC
  • Scripts/Systems/IntimacySystem.cs -- newborns inherit the player's family surname (display-only; parent-matching unchanged)
  • Scripts/Systems/GuildSystem.cs -- OfficerGoldWithdrawLimit 50,000 -> 10,000,000 per transaction
  • Scripts/Systems/CombatEngine.cs -- group follower healer: CombatAction.HealTargetOverride + ParseGroupCombatInput leader param/BuildFollowerHealTargets/PickDefaultHealTarget + C#T#/bare-C/H heal parse; spell-apply override branch + multi-target leader inclusion + shared ApplyHealTo/ApplyBuffTo; follower heal-target display + menu flags; Heal/UseItem quick-mode for followers (no prompt hang)
  • Scripts/Locations/DungeonLocation.cs -- post-combat follower room refresh (DisplayRoomView -> PushRoomToFollowers); /help reconciliation; /party full-party HP panel via new RenderPartyStatus (followers reach the leader's live teammates)
  • Localization/{en,es,fr,it,hu}.json -- 11 keys: 3 combat.follower_heal_*, 3 dungeon.follower_help_*, 5 party.*
  • Scripts/Core/Character.cs -- TeammateDisabledAbilities/TeammateDisabledSpells player dicts + GetSkillToggleKey()
  • Scripts/Systems/SaveDataStructures.cs, Scripts/Systems/SaveSystem.cs, Scripts/Core/GameEngine.cs -- player-dict save round-trip
  • Scripts/Systems/CombatEngine.cs -- teammate ability/spell filter now reads the controlling player's toggles for non-companion teammates (GetDisabledSpellsFor made instance)
  • Scripts/Locations/DungeonLocation.cs -- ManageTeammateSkills editor + PromptManageTeammateSkills picker + [S] option in both the entrance ([T]) and in-dungeon ([Y]) party menus
  • Localization/{en,es,fr,it,hu}.json -- 8 new dungeon.skills_* keys
  • Localization/{en,es,fr,it,hu}.json -- combat.heal_target_option gains the {4}% field; 7 new equip.class_* / equip.weapon_class_line keys; 7 new marriage.surname_* keys
  • Scripts/Systems/StreetEncounterSystem.cs -- town merchant items render from DisplayKey/DescKey/DescArg (English name kept as the purchase logic key); localized price line; ApplyMerchantItem unchanged
  • Localization/{en,es,fr,it,hu}.json -- 16 new street_encounter.merchant.item.* + street_encounter.merchant.price_line keys
  • Scripts/Locations/MagicShopLocation.cs -- SellAccessory fully localized (fence bonus, per-item sell line, sell-which prompt, bulk-sell confirm) via Loc.Get; literal [A]/[F]/0/(Y/N) hotkeys preserved to match the handler's input matching
  • Scripts/Locations/BaseLocation.cs -- FilteredSellFromBackpack "... and N more" overflow line localized (shop.filter_more)
  • Localization/{en,es,fr,it,hu}.json -- 5 new keys: 4 magic_shop.* (fence_bonus / sell_for_gold / sell_prompt / bulk_sell_confirm) + shop.filter_more
  • Scripts/Core/GameConfig.cs -- new GameConfig.IsAffirmative helper (localized yes: Y/S/O/I + YES/SI/SÌ/OUI/IGEN; null-safe; strict superset of the old checks)
  • 43 files (Scripts/Locations/*, Scripts/Systems/*, Scripts/UI/TerminalEmulator.cs, etc.) -- 209 yes/no confirm checks routed through GameConfig.IsAffirmative so the displayed localized accept key works; 18 lookalike sites (menu hotkeys, typed-word confirms) left as-is after classification
  • Scripts/Systems/EndingsSystem.cs -- NG+ begin-prompt validity loop now accepts localized yes (was gating S out before the choice was read)
  • Localization/{es,it}.json -- 8 shop hotkey-display corrections (sell-which [T]->[A], Spanish auto-buy [S]/[O]->[Y]/[S], filter-by-level/cancel [N]/[S]->[L]/[Q]) + está->esta typo
  • Scripts/Locations/MusicShopLocation.cs -- performance + lore song Title/Intro/Verses routed through localized keys (LocOr fallback helper; keyed by song pool+index and by Old God); source lyric em-dashes normalized to ASCII
  • Localization/{en,es,fr,it,hu}.json -- 160 new music.song.* (12 performances) + music.lore.* (7 Old God ballads) keys

Don't miss a new usurper-reborn release

NewReleases is sending notifications on new releases.