github binary-knight/usurper-reborn v0.65.0
Beta v0.65.0

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

Release Notes - v0.65.0 (Countdown)

First release of the Beta -> 1.0 "Countdown" cycle.

Onboarding package (B1) -- aimed at the 87% zero-minute bounce

87% of new online accounts have historically played zero minutes. Nothing measured where they fell (PostHog telemetry was removed in v0.52.12 and never replaced), and the path from connect to gameplay was roughly ten decision screens. Three coordinated changes:

B1a -- Onboarding funnel telemetry. New onboarding_events SQLite table (UNIQUE(username, event) + INSERT OR IGNORE = idempotent writes from hot paths). Five milestones wired: account_created (RegisterPlayer), character_created (first save of a new online character), reached_town (first Main Street entry, session-gated so it costs one DB write per session max), first_kill (MKills 0 -> 1 transition, all three victory paths, with grouped-follower kills attributed to the follower's own account rather than the leader's session), and second_login (first login on a later calendar day, detected in the existing login-streak seam). All writes online-only, fire-and-forget, try/catch-wrapped. New GET /api/balance/onboarding endpoint on the web proxy returns 7-day and 30-day cohort funnels (cohort = accounts created in the window; stages counted within that cohort) plus a per-connection-type split, because the Web drive-by funnel and the Steam-buyer funnel are different populations. The plan: collect 3-5 days of data before any further onboarding surgery, so the next decision (e.g. web guest mode) is made on evidence.

B1b -- Quick Start character creation. Full creation is ~10 decision screens (name, gender, orientation, 10 races with previews, 17 classes with previews, difficulty, 5 stat rerolls, summary, auto-look) before any gameplay. New [Q] Quick Start / [C] Custom fork right after the name step: Quick Start is TWO choices -- one of three curated archetypes (Human Warrior / Elf Magician / Dwarf Cleric) and a numbered gender pick -- with everything else defaulted (orientation Straight, difficulty Normal, single stat roll, AutoLook ON for MUD sessions). A compact summary names the hero and points at [~] Preferences where every defaulted choice can be changed. Target met: under 60 seconds from connect to Main Street. The Custom path is byte-for-byte unchanged. Design note: the gender pick is NUMBERED ([1]/[2]) rather than lettered because gender initials collide across languages (Spanish M = Mujer = female vs English M = male; Hungarian F = Ferfi = male vs English F = female) -- letters cannot be made safe in a shared handler.

B1c -- Journal ticker (Journal Slice 2). The /journal command's NEXT STEP recommendation now also renders as a one-line bright-yellow ticker on Main Street (> Next: Aurelion waits on floor 85. (/journal for more)), right below the street micro-event. This is the push half of the visibility fix -- a new player who has never heard of /journal sees the first-fight arrow on the first screen of the game. Suppressed when the next step is the default "go delve" recommendation so veterans with nothing pending see nothing (signal, not wallpaper). Zero DB cost -- the ladder is a pure in-memory read.

Linux auto-updater actually applies updates now (B3)

The long-standing "Linux x64 BBS auto-updater appears to work but doesn't apply the update" mystery, root-caused: the updater script waited on pgrep -f "UsurperReborn", which matches the pattern against the FULL COMMAND LINE of every process on the box -- a second BBS node in the door, a --mud-relay process, or any process whose argv contains the install path kept the wait loop spinning forever, the copy never ran, and LaunchUpdater discarded all script output to /dev/null so the failure was invisible. Fixed four ways: (1) the script now waits on the launching game instance's actual PID (kill -0), (2) other instances get a bounded 120-second grace window via pgrep -x (exact process-name match, no command-line false positives) and the update proceeds regardless after it, (3) every step logs to update.log in the install directory and bash-level launch failures go to a sidecar .launch.log instead of /dev/null, (4) the script verifies and logs the post-copy version.txt. Generated script validated with bash -n.

Launch hygiene (B5)

  • llm_usage prune wired. The table's creation comment claimed "Pruned to last 30 days" since v0.64.0, but no prune ever existed -- with Slice 11 dialogue flavor writing one row per LLM attempt, the production DB grew without bound. New PruneOldLLMUsage(30) runs in the same world-daily maintenance pass as the npc_decision_log prune.
  • FILE_ID.DIZ version automated. The static file said v0.60.8 [BETA] while the game was at v0.64.2 -- four versions of drift, bundled into every platform zip. The version line is now a {VERSION} template substituted from GameConfig.Version at CI build time, the same source as version.txt, so it can never drift again.
  • Tag/version CI gate. Release zips, the Steam vdf, and the server version.txt all derive from the git TAG while the binary self-reports GameConfig.Version; nothing asserted they matched. A mismatched tag would ship binaries reporting the wrong version and break the auto-updater's comparison. CI now fails a release build when tag != v{GameConfig.Version}.
  • The title-screen BETA banner intentionally stays until the actual 1.0 build (it is correct today); the flip is tracked as a release-day task.

Late-game combat tension restored (B2): bounded monster accuracy

The twice-flagged, twice-deferred "damage taken at floor 30+ is literally 0" problem, finally root-caused: player Hit-Roll AC (10 + DEX/3 + ArmPow/25 + Level/10) grows with stats and gear so much faster than monster accuracy (Level/2 + Str/3) that by floor ~30 a geared player's AC exceeds the monster's BEST POSSIBLE roll -- monsters could only hit on natural 20s (5% of swings, and those landed as crits, which is why the rare hits looked spiky). The longest stretch of the game ran on autopilot.

Fix: bounded accuracy at the single chokepoint every PvE monster basic attack routes through (TrainingSystem.RollMonsterAttack). The NATURAL ROLL NEEDED is clamped, not the stats: an at-level monster (within 5 levels of the player) now always lands at least 25% of swings and never more than 90%; under-leveled monsters (the player descending to farm) keep their natural near-zero hit rates with only a 10% universal floor. Early game is untouched -- low-floor needed-rolls already fall inside the clamp window, and a squishy at-level player's natural 80% incoming hit rate is unchanged (the clamp only lifts the absurd tails). Tank investment still matters: it moves you from the natural rate down to the floor, and every per-hit damage-reduction layer is unchanged. Four new GameConfig constants (AtLevelAccuracyGap, MonsterAccuracyMinNeeded, MonsterAccuracyMaxNeededAtLevel, MonsterAccuracyMaxNeededWeak) make the window tunable from telemetry. Companion-targeted monster attacks deliberately untouched.

Note: current telemetry was too thin for data-driven tuning (only 16 logged fights above floor 20 in the 7-day window -- the population is mostly early-game), so this pass is analytic, derived from the actual formulas; the dashboard's per-band damage-taken numbers will verify it live.

Strongly-recommended batch (1.0-prep SR items)

  • LLM daily budget survives restarts. The in-memory token counter zeroed on every deploy-restart, making the 500k/day cap soft. New LLMBudget.RehydrateFromBackend reconstructs today's spend from the llm_usage telemetry (a single SUM on an indexed column) at MudServer startup.
  • Per-IP login brute-force throttle. The 5-attempt cap was per CONNECTION -- reconnecting reset it, so password guessing ran at TCP-reconnect speed. New per-effectiveIp failed-login tracker (same identity the ban system uses): 10 failures triggers an exponential lockout (60s doubling, capped 15 min), success forgives, records decay after an hour idle, loopback exempt. Enforced at both the AUTH-line and interactive login paths.
  • Monster summon abilities actually summon now. The generic SummonMonsters flag -- set by SEVEN ability handlers (SummonMinions and friends) since the ability system shipped -- was read by nothing: "calls for reinforcements!" printed and no monsters appeared (the v0.62.0 combat review caught it; Vex's Gauntlet kit was swapped to Curse as a workaround). Deeper than it looked: the live monster list was never passed into the ability pipeline (hardcoded null), which ALSO silently no-op'd the named boss-summon path (Maelketh's "Summon Soldiers", Aurelion's "Summon Executioners", Noctura's "Shade Clones" -- all wasted boss turns). Fixed end-to-end: the live list is plumbed through ProcessMonsterAction -> TryMonsterSpecialAbility -> MonsterAttacksCompanion, a shared TrySpawnSummonReinforcements helper fires on BOTH the player-target and companion-target paths (with a full party, monsters mostly target companions -- the fix would have been silently inert most turns without this), the ability's own SummonCount is respected, and everything is capped (5 active monsters for regular fights, 6 for boss fights -- including the two previously-uncapped hardcoded Maelketh phase-summon sources, which now stack with the live ability path and needed the ceiling). Summons are worth floor XP and no gold, so no reward farming. New combat.summoned_minion_name / combat.reinforcements_arrive keys in 5 languages.
  • Roll display relabeled. With bounded accuracy, the DC shown in the monster roll line is the clamped effective value, which can differ from the player's real Hit Roll AC -- the label "vs Hit Roll AC {N}" would have been a lie. Relabeled to "vs DC {N}" (per-language equivalents) in all 5 languages; the status bar keeps showing the true Hit Roll AC.
  • AddLover cap UX: verified already complete -- all five callers check the rejection and surface player-facing messages (the audit item was stale).
  • Both combat changes (bounded accuracy + summon wiring) passed a dedicated combat-reviewer audit; its three findings (companion-path parity, boss summon caps, SummonCount respect) are incorporated above.
  • Sellsword Hall tier-2 contracts. The freelance merc board's 150-contract Legend ladder was fed by only 6 tier-1 templates -- 50 days of the same handful of jobs. Added 6 tier-2 contracts (2 per faction: Crown warband-breaker + deep survey, Shadows message-job + route-scout, Faith ghoul-cleansing + lantern-vigil), unlocked at Sellsword rank (10 completions) via the board's existing Tier <= max(1, rankIdx) visibility gate -- zero new gating code. Tier-2 payout multipliers (1.40-1.60) visibly out-earn tier-1 (0.85-1.00) so climbing the ladder pays. Kill targets use monster names that actually spawn in their level bands (Orc, Ghoul); the rest lean on room-exploration / generic-kill objectives that have no spawn dependency. Gold still scales on player level only (no wealth compounding). 18 new loc keys (6 contracts x 3 strings) in 5 languages. Tiers 3-5 + Legend's Pick stay deferred to post-release.
  • World-state write race closed (the v0.61.2 class). The world sim's save cycle reads world_state, spends hundreds of milliseconds capturing/merging/serializing an ~18 MB NPC blob, then blind-wrote it back -- a player session's SaveAllSharedState (recruiting an NPC, equipping a teammate, marrying) landing in that window was silently clobbered by the sim's stale snapshot. The v0.61.2 recruit-side fix eliminated the observed symptom; this closes the hole itself. New SqlSaveBackend.SaveWorldStateIfVersion(key, json, expectedVersion) -- a transactional compare-and-set (BEGIN IMMEDIATE, so the version check and write are atomic against all writers) that refuses the write if anyone bumped the version since the sim's read. The sim's NPC and royal_court writes both route through it: a conflict turns the old clobber into a skipped cycle, and the next cycle's existing version-check machinery reloads + merges (pregnancies, equipment, engaged-NPC protection) before re-saving. Version baselines are computed locally from the CAS contract (expected -> expected+1) rather than re-read, because a re-read could adopt a concurrent player write's version and skip the reload that write was owed -- the dedicated save-state-reviewer audit caught exactly that interleaving (F1) plus the unguarded royal_court window (F4); both fixed in-patch, along with two zero-edge cases (row created by another writer between sim startup and first save) and a reload-branch baseline adoption without which every post-reload guarded save would have failed its guard forever. Children/marriages/events/settlement keys stay blind-write for now (the sim serializes them from shared in-process singletons, so content is fresh in the single-process MUD deployment); session-vs-session CAS retry is a flagged post-1.0 follow-up (reviewer F5/F7).

Permadeath no longer cross-links a recreated same-name character

Player report: a character died (online permadeath), the player recreated with the same name but different capitalization, and the new character inherited the dead one's active quests (already in progress, never picked up) and arena wins (the website leaderboard showed the new name carrying the old win count). Root cause was NOT the capitalization (the SQL layer already folds both casings to one lowercase account row, so the permadeath delete worked) -- it was that permadeath did not fully erase a character's identity-linked state that lives outside the players row, and a same-name recreation re-bound to the leftovers:

  • Quests lingered in the shared quest pool + world_state["quests"] (permadeath never purged them) and re-attached by occupier name. Worse, the recreation's quest-cleanup was gated on "the new save already has quests" -- a fresh character has none, so the cleanup never ran. Fixed three ways: permadeath now removes the dead character's claimed quests and durably pushes the cleaned list to world_state; the recreation-side merge runs unconditionally; and after the world-state quest merge on load, the game reasserts that the player's claimed quests equal exactly their own save (this last one also self-heals characters already affected before this build, like the reporter's).
  • Arena/PvP wins survived in pvp_log (deliberately excluded from the permadeath purge in v0.60.5) keyed by account name, so the leaderboard re-joined them to the recreation. Permadeath now purges the erased character's pvp_log wins. Purge is win-rows-only so a still-living opponent's win-against-this-character is preserved, not deleted out from under them.
  • God worship + relationships were keyed by display name in gameplay but the permadeath purge-hook passed the account username, so the clear silently missed when the two differed (latent same-class bug). The hook now clears by display name (and account name as a fallback).
  • Children were not disowned on permadeath (only on the menu-delete and NG+ paths), so a same-name recreation could re-inherit them and their family bonuses. Permadeath now disowns via the ID-first path and pushes the change to world_state.

Two save-state-reviewer passes (root-cause + implementation); both implementation findings (the world-quest re-injection window, the pvp_log opponent-win collateral) fixed in-patch. No schema or save-format change; existing mixed-case rows are unaffected.

Home "resurrect dead lover" no longer charges gold for an already-respawned NPC

Player report (single-player): an NPC lover was killed by an assassin and offered for resurrection at Home, but the same NPC was simultaneously alive at the Temple. Root-caused (npc-system-reviewer) as a time-of-check race, not a duplicate NPC: single-player NPC permadeath is disabled, so dead NPCs auto-respawn ~10 minutes later (the resurrect-at-Home feature is really "pay to skip the respawn wait"). The Home resurrect screen snapshotted the dead-lover list and then blocked on player input; during that wait the background world-sim respawned the NPC (cleared IsDead, restored HP, sent them roaming -- to the Temple), so the stale screen still offered a resurrection the world had already granted for free. Picking them charged gold to set IsDead=false on a live NPC -- a paid no-op. Fix: HomeLocation.ResurrectAlly now re-validates the chosen NPC's live state after the input await, and if they have already recovered it says so and charges nothing. One new home.resurrect_already_recovered loc key in 5 languages. (Reviewer confirmed the romance state itself is correct and untouched across respawn -- no save or relationship-system change needed.)

Antidote now recognizes poison from dungeon discoveries

Player report: examining the "Gas Choked Passage" discovery says "you have been afflicted with poisoned," but using an antidote says "you are not poisoned." Root cause (workflow + combat-reviewer): the game has two independent poison representations -- the canonical out-of-combat Character.Poison/PoisonTurns counter (what every antidote and the healer cure read) and the in-combat ActiveStatuses[Poisoned] DoT. The discovery affliction set only the latter, so the affliction showed on /health while every cure checked the former and found nothing. Fix routes a discovery-applied Poisoned status to the canonical Character.Poison field (exactly like the existing dungeon poison-dart trap) so it ticks, displays, and cures consistently. Critically it sets that field instead of the status, not in addition: combat ticks the two representations as separate per-round DoT sources, so setting both would have double-damaged a discovery-poisoned player in combat (caught during review). The three cure sites (the /antidote command, the in-dungeon [D] antidote, and the Healer's cure) additionally now recognize and clear a lingering combat-applied ActiveStatuses[Poisoned], so a poison that carried out of a fight is curable too. Other discovery statuses (Frozen/Stunned/Bleeding) are unchanged. combat-reviewer verified: one tick source, dart-trap parity, monster poison untouched, no HasStatus(player Poisoned) readers regressed.

">" no longer rejected when cycling loot-compare party members on MUD/relay clients

Player report: in the dungeon loot-compare screen, "<" (previous member) cycles but ">" (next member) returns "invalid response." Investigation (workflow + manual read) found the two branches are byte-for-byte symmetric in source -- same gate, same wrap math, clean ASCII literals, byte-clean transport -- so it is not a missing-case bug and is not deterministically reproducible from source. The only viable runtime cause is the single-key reader (TerminalEmulator.GetKeyInput) taking input[0] with no trim: a MUD/relay client that prepends a stray whitespace/CR byte before ">" (but not "<", a keyboard-chord timing artifact) would make input[0] the stray byte and fail the ">" match. Fix trims the line before taking the first character in every GetKeyInput line-input branch -- strictly correct for a single-key read and the only place ">" could diverge from "<" at runtime. Medium confidence on it being THIS player's exact cause (source is provably symmetric); the hardening is safe regardless and improves every single-key prompt. If it recurs, the next step is the reporter's client details.

Bank player-to-player wire transfers

Player request. The bank's [T] Transfer previously only let you send gold to NPCs (it credited an NPC's cash, which is useless), so there was no in-bank way to send money to another player. Replaced with a proper player-to-player wire (online mode): pick a player by name, enter an amount from your bank balance, and it auto-deposits into their bank account on their next login. The bank takes a 2% fee that is removed from the economy (a small gold sink, consistent with how shops are already taxed); the recipient receives the amount minus the fee. Built on the proven v0.61.5 inheritance-delivery pattern: the gold is queued in a new pending_gold_transfers table and the recipient's OWN session applies the credit on login -- never a cross-session write to the recipient's live save (the dupe/clobber vector that pattern exists to avoid). Anti-dupe ordering: the sender's debit is persisted before the credit is queued, so the only crash window loses gold to the sink rather than duplicating it. Self-transfers and unknown recipients are rejected; single-player shows "online service only." save-state-reviewer verified no dupe path, consistent username keying across session casing, and additive persistence; its one finding (orphaned transfer rows if a recipient is deleted before delivery) is handled by adding pending_gold_transfers and pending_inheritance to the orphaned-row cleanup sweep. 16 new loc keys in 5 languages.

Scripted Stranger encounters localized (the on-death crone, and the other five)

Player report (Hungarian session): the old-crone encounter that appears after you die rendered in English. Root cause: the six SCRIPTED Stranger encounters -- AfterFirstDeath ("THE RETURN", the crone), AfterCompanionDeath, AfterFirstOldGod, TheMidgameLesson, TheRevelation, PreFloor70 -- were hardcoded English in StrangerEncounterSystem.ScriptedEncounters and written directly by DisplayScriptedStrangerEncounter. The v0.61.2 stranger-localization pass covered the random ContextualDialoguePool but never these scripted death/milestone beats. Fixed the whole subsystem (so the other five don't generate repeat reports): all title / intro / dialogue / response / reply / closing lines now resolve through stranger.scripted.{type}.* keys, with the hardcoded English kept as a positional fallback (renders English even if a key is missing); the disguise name/description reuse the existing stranger.disguise.{enum}.* keys. 151 new keys extracted and translated into all 5 languages (es/fr/it/hu via parallel translator agents; Hungarian was the load-bearing target). Proper nouns (Noctura, Manwe) kept English; "--"/"..."/"stage directions" preserved; no em-dashes; no format args. Build clean, 845/845.

Quick Start creation: validates input and lets you back out

Player report on the new B1b Quick Start: you couldn't back out of it, and invalid input silently defaulted (typing "g" to every prompt produced a Human Warrior with no signal). Both were real -- the original code deliberately mapped garbage archetype input to option 1 and any non-"2" gender to Male, "to never bounce them out," which instead produced silent wrong characters. Fixed: the mode (Q/C), archetype, and gender prompts now re-prompt with a clear message on invalid input instead of defaulting, and a [0] Back option steps back (archetype-back exits Quick Start into the full Custom flow; gender-back returns to the archetype list). 4 new loc keys in 5 languages.

Crash fix: intimate encounter in pro-drop languages (Hungarian/Spanish/Italian)

Player report (Hungarian session): starting an intimate encounter with an NPC partner from the spend-time-with-partner screen crashed with "Index and length must refer to a location within the string." Root cause: the v0.61.5 pronoun work made GetLocalizedSubjectPronoun/GetLocalizedPossessivePronoun return an EMPTY string for pro-drop languages (hu/es/it), but several sites still capitalized the first letter with the manual idiom their.Substring(0,1).ToUpper() + their.Substring(1) (and spouseGender.ToUpperInvariant()[0] + spouseGender.Substring(1)), which throws on an empty string. Most pronoun uses were safe because they route through CleanFormat (which guards empty), but nine capitalize sites did not. Fixed with a single safe GameConfig.CapitalizeFirst helper (returns the input unchanged when empty) applied to all nine: 3 in IntimacySystem (the reported crash), 1 in RelationshipSystem, 4 in HomeLocation (hardcore/affair spend-time flows), 1 in DungeonLocation. No loc changes; pure crash fix. English/French (which have non-empty pronouns) were unaffected and still capitalize correctly.

Gang War / PvP: player now dual-wields and lifesteals

Player report: in Gang Wars "the fight system still uses something old -- I didn't attack twice, my life steal didn't." Gang war combat routes through CombatEngine.PlayerVsPlayer, whose player-attack path (ExecutePvPAttack) was a single bespoke swing with no dual-wield and no lifesteal -- both of which the PvE path has. Rewrote it to mirror PvE: the swing count now comes from GetAttackCount (dual-wield off-hand + class extra attacks + windfury), off-hand swings take the same reduced-power modifier (GetWeaponConfigDamageModifier(attacker, isOffHand)), and each hit applies per-hand lifesteal (GetEffectiveLifeStealPercent, already capped). So a dual-wielder swings twice and a Lifedrinker weapon heals on hit in gang wars, arena, and faction ambushes. combat-reviewer verified dual-wield parity with PvE, the death-guard loop, and lifesteal cap/attribution. KNOWN LIMITATIONS (deferred, documented): the NPC opponent's turn runs a separate handler and is NOT yet symmetric (still single-swing, no lifesteal) -- in the player's favor, and rare since NPCs seldom carry such gear; and the other weapon enchants (elemental/thorns/Marked) plus the richer PvE damage formula (level/proficiency/buff scaling) remain PvE-only, because that pipeline is Monster-typed. Those are a follow-up PvP-parity pass, not part of this fix.

Dungeon feature lore localized (epitaphs, "I've lived before", etc.)

Player report (Hungarian session): the dungeon-feature lore -- the epitaph and the "they told me I had lived before" scratched-wall message -- rendered in English. FeatureInteractionSystem (kept by v0.62.0 for non-catalog filler features) has ~40 hardcoded-English LoreFragment text blocks (theme lore, per-Old-God lore, generic lore, memory + ocean-insight fragments, ~145 lines) picked at random and printed directly. Because they're random with no stable per-fragment key, localized them with a content-hash scheme: each displayed line resolves feature.lore.<sha256(line)[:12]>, with the English line as fallback (so English always renders even if a key is missing). A generator seeds en.json from the source using the identical hash the C# computes at display. 145 keys translated into all 5 languages (Old God names / Worldbreaker / Cycle / Wave / Ocean handled per each language's established lore glossary). In passing, the 4 lore lines that carried em-dashes in the original source were cleaned to -- (source + all 5 languages, keys re-hashed accordingly) to comply with the no-em-dash rule. Build clean, 845/845.

Remaining 1.0 blockers (tracked in project_release_1_0.md)

All build-side blocker and strongly-recommended items are done. What remains is release-day work (BETA banner flip, store-page pass) plus telemetry-gated tuning: the B2 floors-30+ offense fine-tune and the class-balance read once the new funnel + accuracy data accumulates (3-5 days). B4 SSH-path IP-ban shim descoped per user. Reviewer follow-ups F5/F7 (session-side world-state CAS retry, interface promotion) are post-1.0.

Files Changed

  • Scripts/Core/GameConfig.cs -- Version 0.65.0 "Countdown"
  • Scripts/Systems/SqlSaveBackend.cs -- onboarding_events table + RecordOnboardingEvent (idempotent) + account_created write in RegisterPlayer; PruneOldLLMUsage; SaveWorldStateIfVersion transactional CAS
  • Scripts/Core/GameEngine.cs -- character_created write (first save of new online character); second_login write (login-streak seam)
  • Scripts/Locations/MainStreetLocation.cs -- reached_town write (session-gated); Journal next-step ticker
  • Scripts/Systems/CombatEngine.cs -- RecordFirstKillFunnel helper + first_kill writes at all three victory paths (grouped-follower attribution correct)
  • Scripts/Server/SessionContext.cs -- OnboardingTownRecorded session gate
  • Scripts/Systems/CharacterCreationSystem.cs -- TryQuickStart (archetype + numbered gender pick + defaults + compact summary); Quick/Custom fork after the name step; v0.65.0: input validation (re-prompt, no silent default) + [0] Back step-back navigation
  • Scripts/Systems/VersionChecker.cs -- Unix updater rewritten (PID wait, bounded pgrep -x grace, full logging, version verify); LaunchUpdater sidecar launch log
  • Scripts/Systems/WorldSimService.cs -- llm_usage prune call in world-daily maintenance; NPC + royal_court writes routed through CAS guard with local version increment, conflict-skip path, zero-edge baseline adoption, reload-branch version adoption
  • Scripts/Systems/QuestSystem.cs -- 6 tier-2 MercTemplate entries; RemovePlayerQuests + ReconcilePlayerQuests (permadeath/recreation identity cleanup)
  • Scripts/Systems/PermadeathHelper.cs -- permadeath now purges quests + disowns children (ID-first) + passes display name to the world-state purge
  • Scripts/Systems/SqlSaveBackend.cs -- PurgePlayerWorldState gains displayName param + pvp_log (winner-only) purge; PermadeathPurgeHook widened to (username, displayName)
  • Scripts/Server/MudServer.cs -- purge-hook clears god worship + relationships by display name (was account username)
  • Scripts/Systems/OnlineStateManager.cs -- SaveSharedQuestsNow targeted world-state quest push
  • Scripts/Core/GameEngine.cs -- unconditional quest merge on load + post-world-merge stale-quest reconciliation
  • Scripts/Locations/HomeLocation.cs -- ResurrectAlly re-validates target after the input await (no paid no-op resurrect of a respawned NPC)
  • Scripts/Systems/DiscoverySystem.cs -- discovery Poisoned status routes to canonical Character.Poison field (not ActiveStatuses), avoiding combat double-tick
  • Scripts/Locations/BaseLocation.cs -- /antidote recognizes + clears lingering ActiveStatuses[Poisoned]
  • Scripts/Locations/DungeonLocation.cs -- [D] antidote recognizes + clears lingering ActiveStatuses[Poisoned]
  • Scripts/Locations/HealerLocation.cs -- CurePoison gate + cure recognize/clear lingering ActiveStatuses[Poisoned]
  • Scripts/UI/TerminalEmulator.cs -- GetKeyInput trims line input before taking input[0] (single-key read robustness)
  • Scripts/Locations/BankLocation.cs -- [T] Transfer rewritten as player-to-player wire (online, 2% fee, debit-then-queue)
  • Scripts/Systems/SqlSaveBackend.cs -- pending_gold_transfers table + Queue/Get/Clear methods; orphan-cleanup sweep covers pending_gold_transfers + pending_inheritance
  • Scripts/Core/GameEngine.cs -- DeliverPendingGoldTransfers (login auto-deposit to bank)
  • Scripts/Core/GameConfig.cs -- BankTransferFeePercent = 0.02; CapitalizeFirst safe helper (empty-pronoun crash fix)
  • Scripts/Systems/IntimacySystem.cs, Scripts/Systems/RelationshipSystem.cs, Scripts/Locations/HomeLocation.cs, Scripts/Locations/DungeonLocation.cs -- 9 unsafe capitalize-first sites routed through GameConfig.CapitalizeFirst
  • Scripts/Systems/CombatEngine.cs -- ExecutePvPAttack split into a swing-count loop + ExecutePvPSingleHit (PvP dual-wield + per-hand lifesteal)
  • Scripts/Systems/FeatureInteractionSystem.cs -- ResolveLoreLine/LoreLineKey content-hash resolver at the 3 lore display sites; source lore em-dashes cleaned to --
  • Localization/{en,es,fr,it,hu}.json -- 145 feature.lore.* hashed dungeon-lore keys
  • Scripts/Locations/BaseLocation.cs -- DisplayScriptedStrangerEncounter resolves stranger.scripted.{type}.* keys (English fallback) + reuses disguise keys
  • Localization/{en,es,fr,it,hu}.json -- 151 stranger.scripted.* keys (6 scripted encounters)
  • Localization/{en,es,fr,it,hu}.json -- home.resurrect_already_recovered; 16 bank.wire_* / bank.transfer_online_only / engine.wire_* keys
  • web/ssh-proxy.js -- GET /api/balance/onboarding funnel endpoint
  • .github/workflows/ci-cd.yml -- FILE_ID.DIZ version templating; tag/version release gate
  • dist/FILE_ID.DIZ -- version line templated to {VERSION}
  • Localization/{en,es,fr,it,hu}.json -- 15 creation.quick_start*/creation.qs_* keys + journal.ticker per language

Don't miss a new usurper-reborn release

NewReleases is sending notifications on new releases.