Skip to content

Releases: binary-knight/usurper-reborn

Beta v0.65.6 Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 12 Jul 07:32

Release Notes - v0.65.6 (Countdown)

Eighth release of the Beta -> 1.0 "Countdown" cycle. Two major passes in one release. Part 1 is
a focused NPC-behavior pass driven by a full deep-dive into the live decision telemetry (~331k NPC
decisions over 14 days) plus a code-level read of the whole decision pipeline (candidate builder,
Brain v2 scorer, abstract combat sim, Tier-A real combat sim, LLM strategic-goal flow). Two goals:
NPCs make good, life-preserving choices, and the LLM-backed brain drives behavior for the whole
population. Part 2 is the first slice of the player-experience overhaul driven by the
end-to-end analysis in DOCS/PLAYER_EXPERIENCE_ANALYSIS.md: renewable resurrections, burst-death
protection, and visible risk (see "Part 2" below).

Part 1: NPC behavior

What the telemetry showed

  • ~1,124 NPC dungeon deaths in 14 days (about 14% of expeditions ended in death). NPCs fought until
    20% HP before even rolling to flee, with no awareness that the next hit would kill them; most deaths
    were "failed one flee roll while one swing from death."
  • A near-death NPC learned nothing: dungeon losses never wrote a memory, so the scorer's
    "recently attacked, avoid combat" caution layer (built in v0.64.0) never fired for monster fights,
    and an NPC that barely escaped would heal and walk straight back in.
  • 98.2% of shop visits bought nothing (84,858 fruitless trips): the built-in shop stock tops out
    around 80 weapon power, but the scorer expected Level*12, so every NPC above about level 10 was
    flagged "under-geared" forever and sent window-shopping for eternity.
  • 96.7% of gym visits did nothing: the candidate gate required 20 gold but a session costs
    Level*10+50, so mid-level NPCs kept walking to a gym they could not afford.
  • Good news: ~91% of the population was already on the Brain v2 goal-driven scorer, and LLM strategic
    goals are flowing into behavior (goal-family verb boosts + physical target pursuit) now that the
    API key works again.

Life preservation

  • Predictive-death flee. All three combat simulations (solo abstract, team, and the Tier-A real
    combat sim) now estimate the enemy's expected next hit using the exact damage math that combat
    actually applies. If the NPC could not survive roughly 1.5 such hits, they panic-flee at a flat 95%
    regardless of personality; self-preservation beats Courage at the brink. Fights the NPC is winning
    are untouched -- the existing 20% flee threshold still governs those, so kill-completion (the reason
    the threshold was lowered in v0.63.2) is preserved.
  • Survival lessons. Dying (NPCs respawn from ordinary deaths, and the brain survives the respawn)
    or fleeing a fight below 35% HP now writes a new "survived danger" memory. The Brain v2 scorer's
    existing combat-caution layer reads those memories for 4 hours and discounts dungeon / team-dungeon /
    dark-alley / throne-challenge verbs, so a mauled NPC spends the afternoon healing, shopping, and
    socializing instead of marching straight back down the stairs. This closes the gap explicitly
    deferred in v0.64.0 Slice 3 ("dungeon-loss outcomes don't currently write Attacked memories").
    A near-death also now registers as fear in the NPC's emotional state for the same 4-hour window.
    (Review note: the lesson uses a NEW memory type rather than reusing "Attacked" -- the Attacked type
    feeds the revenge-goal generators, which assume the attacker is a real person; a monster name there
    would have minted instantly-self-completing "revenge against Golem" goals and spammed the news feed.)
  • Player-team guard. NPCTeamDungeonRun now has an explicit player-team early-return (previously
    it relied entirely on upstream dispatcher gates; a future dispatcher change could have silently
    exposed player teammates to autonomous dungeon deaths).

Realistic decisions (no more do-nothing loops)

  • Shops can say "come back later." A shop visit that ends with no purchase marks a 6-hour
    "already checked the shops" cooldown that the scorer respects. The gear-gap shopping boost now only
    fires at level 12 and below, the band where the built-in shop stock can actually close a gap; above
    that, gearing comes from dungeon loot and level-up gains, exactly as the game already provides it.
    And a shopping trip now checks BOTH the weapon and armor shops before giving up, instead of flipping
    a coin and browsing only one.
  • The gym gate matches the price. NPCs only head to the gym when they can afford the session.
  • Whole-population brain. The remaining ~9% of NPCs still on the legacy weighted-random picker are
    promoted to the Brain v2 goal-driven scorer. The v0.64.0 A/B cohort served its purpose; there is no
    reason to keep a slice of the world dumber than the rest. LLM strategic goals, need-driven scoring
    (heal when hurt, level when ready), and target pursuit now apply to everyone. (Review note: the
    promotion is applied where the live roster is actually built -- both NPC restore sites -- because the
    MUD server never runs the world-sim's startup path and reloads NPCs from shared state continuously;
    a startup-only backfill would have been dead code on the live server.)

LLM brain status (server-side, no code change)

The deep-dive confirmed the strategic-goal pipeline is healthy end to end: the LLM proposes long-arc
goals per NPC, the scorer biases verb selection toward the active goal family, and targeted goals
steer NPCs to physically seek out the person their goal names. Two operational notes: the API key
outage (June 17 to July 11) meant every call fell back to templates for three weeks -- worth watching
the dashboard's LLM tab after any key rotation; and the 3-second request timeout is marginal for
strategic-goal calls (historical successes averaged 2.4s), so raising USURPER_LLM_TIMEOUT_MS to
10000 in the systemd drop-in is recommended at next deploy.

Part 2: Player experience (the level-40 cliff)

Renewable resurrections

Lives now regenerate. Two paths, both online-permadeath mode only (single-player death still uses
the Veil-of-Death penalty menu and never consults the resurrection counter):

  • One life back at every 10th level. Reaching level 10, 20, 30, and so on restores one lost
    resurrection, capped at the character's maximum (normally 3). Applied at the single level-up
    chokepoint so every path (auto-level, Level Master, grouped dungeon combat) grants it exactly
    once per decade crossing. The grant is announced at the next clean screen boundary.
  • The Temple's Rite of Return. A new [U] Rite of Return service at the Temple restores one
    lost life for steep, level-scaled gold (10,000 + level squared x 100: about 50,000 at level 20,
    100,000 at level 30, 170,000 at level 40 -- roughly 45 fights of gold income in the mid-30s, so
    death now costs something recoverable instead of something irreplaceable). One rite per 20
    real-world hours per character (wall-clock cooldown, relog-proof, persisted in the save). The
    option is hidden while lives are full, and the purchase saves immediately so a crash cannot eat
    a six-figure life.

The death screen now says so: after a resurrection is consumed, a hint line explains both recovery
paths, so the countdown reads as a setback instead of a death spiral.

Design intent: the resurrection economy changes from an absorbing chain (every character
mathematically guaranteed to run out) into a renewal process. Careful players trend life-positive;
death still stings (a life or a pile of gold) but no longer compounds toward inevitable deletion.

Death's Door (burst protection)

Telemetry showed every real player death in the retained window was a one-round burst: 100-140% of
max HP in a single round, most often a boss right after its opening damage cap expired. The
existing Last Stand rule (started the round above 50% HP -> cannot be reduced below 1 HP) expired
exactly when needed, because attrition had already pulled the player into the 25-50% window before
the burst landed.

New rule: once per combat, a killing blow against a player who started the round above 25% max
HP leaves them at 1 HP instead ("Death's Door") and the fight ends with them alive -- staggering
out at 1 HP to heal, restock, and choose their next move. The guarantee is spent for that combat;
a later burst in the same expedition kills for real, and a player who engages a round below 25% HP
has accepted the risk, exactly as before. Attrition difficulty is unchanged; only the
no-counterplay one-round deletions are gone. Skipped in Nightmare difficulty and PvP (same as Last
Stand) and in exhibition combat (Gauntlet, Tournament of Honor, pit fights) and arrest combat --
exhibition waves are each a fresh combat, so an unguarded once-per-combat rescue would have reset
every wave and quietly bypassed the Gauntlet's deliberate death-roll stakes (caught by the
combat-reviewer audit and fixed before ship; those surfaces never consume a resurrection anyway).
Grouped parties see a broadcast line when it fires.

Visible risk

Three legibility changes so danger is informed instead of ambush:

  • Lives in the status bar. In online permadeath mode the standard status line now shows
    Lives: N/M everywhere, color-coded (green at 2+, yellow at 1, bright red at 0). Previously the
    counter was only visible on death screens.
  • Floor danger ratings. Entering a dungeon floor above your level now prints a rating on the
    floor banner -- Caution (+1-2 levels), Dangerous (+3-5), DEADLY (+6 or more) -- and a compact
    colored tag (e.g. DEADLY +8) rides the in-dungeon status bar for every room, covering all
    floor-change paths (entry, stairs, floor select).
  • Boss burst telegraph. The round after a boss's first-3-rounds damage cap expires (the exact
    moment most deaths happened), the ...
Read more

Beta v0.65.5 Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 11 Jul 04:59

Release Notes - v0.65.5 (Countdown)

Seventh release of the Beta -> 1.0 "Countdown" cycle. This one is the first pass of correctness
fixes coming out of the full pre-1.0 readiness audit (see DOCS/RELEASE_1_0_AUDIT.md). It clears all
three Tier-0 blockers that audit found plus six of the seven Tier-1 items (the seventh, the in-game
Beta banner flip, is deliberately a release-day action), a first slice of Tier-2 (save caps + a
documentation accuracy pass), and a Linux terminal fix. No new content; this is a bug-fix and
correctness release ahead of the 1.0 tag.

Tier 0: the blockers

Old God AoE damage was ignoring boss immunity and divine armor

Every single-target hit against an Old God correctly ran through the boss's phase-immunity (physical
or magical, depending on the phase) and divine-armor reduction. Area-of-effect damage did not: AoE
spells and abilities (Fireball, Chain Lightning, the Tidesworn's Maelstrom of the Faithful) went
straight to the health bar, so a caster could nuke a boss during an immune phase and shrug off the
divine armor the fight is built around. AoE now applies the same immunity and divine-armor reduction
the single-target path does, per target, in the same order. Maelstrom of the Faithful is treated as
magical for this purpose (it is a faith spell).

Save contamination could wipe seals, worship, companions, and family

When a save ran on the online (MUD) server with no active player session attached, the story, worship,
companion, and family systems all fall back to shared process-wide state, so the save could persist
empty or another player's data over the real character's collected seals (and their worshipped god,
companion roster, and family). Prior builds only logged this condition; the save still went through. It is now
refused outright when it happens: the on-disk save is left untouched and the anomaly is logged. Normal
play always has a session attached, including the save on logout, so this only ever fires on the
anomalous path it is meant to catch. Single-player is unaffected.

Background NPC deaths caused phantom widowhood and stranded lovers

When a rare "permanent" death rolled in the background world simulation (a brawl, a gang or team war, a
high-tier dungeon run), the game fired the full consequence cascade (end the marriage, mail the player
spouse a widowing notice, orphan the children, seed a family grudge) but a separate respawn path
brought the NPC back to life minutes later, because the permanent-death flag had been left disabled.
So a player could be told their spouse had died, then run into that spouse alive. Separately, when a
lover or friend-with-benefits died permanently in the background, they were never released from the
player's relationship state and their affair records lingered forever (only the direct-player-kill path
cleaned these up). Both are fixed: a rolled permanent death is now genuinely permanent (the NPC does
not respawn), and the romance cleanup that the direct-combat path already did now runs on every
permanent death, including old age, releasing lovers and clearing affairs, not just marriages. The rare
rolled permadeath stays rare and floor-gated (it will not push any race toward extinction); the common,
non-rolled death still respawns exactly as before.

Tier 1

  • Marriage and divine-blessing XP bonuses now actually apply. The 10% married-player XP bonus and
    the divine-blessing XP bonus lived only in an old combat path that is no longer reached, so married
    and blessed players had silently been getting neither. Both now apply on every kill and show on the
    reward screen, and the partial-victory (retreat-after-some-kills) path was given the same bonuses.
    Known limitation: grouped co-op FOLLOWERS still do not receive either bonus on group kills (their
    marriage and blessing state lives in their own session, not the leader's, and is not reachable from
    the leader's reward calculation); followers get both bonuses normally on their own solo kills.
  • Non-English monster-encounter text was rendering as raw template. In Spanish, French, Italian,
    and Hungarian, the "a monster appears" and "boss blocks your path" lines had stale templates that
    referenced a variable the game no longer passes, so those languages showed literal placeholder text
    on every encounter. Fixed. Three other player-facing lines (the already-married refusal and the two
    daily-murder-cap notices) that were missing in all four languages are now translated, and a Hungarian
    training-reset line that showed a raw placeholder is fixed.
  • Royal orphans could vanish at population cap. When an orphan came of age while the world was at
    its population cap, the game removed them from the orphanage and the family registry before checking
    whether there was room to create their adult NPC, so at cap they were deleted with no NPC ever made.
    They now wait in the orphanage and graduate on a later tick when a slot opens, matching how regular
    children already behave.
  • Church "this month" figures no longer reroll every visit. The community donation/marriage/
    blessing/soul counts on the Church records screen were random each time you opened it. They are now
    steady within an in-game month and roll over monthly. (Your own church history was always real.)
  • Silent save failures are now logged. Several save and autosave calls (bank wire rollback, ending
    completion, pantheon favors, and the save-recovery file scan) swallowed their errors with no trace.
    They now log, so a genuine save failure is visible instead of silently losing progress.
  • NPC teammate bags and shop stock are now capped in saves. The per-NPC personal inventory and
    market-stock lists were the one per-NPC collection with no serialization cap, so a long career of
    handing items to the same NPC teammate could slowly grow the save (multiplied across the ~200-NPC
    world). Both are now capped like every other per-NPC list, and the online NPC serializer also caps
    the grudge (Enemies) list for parity with the single-player path.

Linux "all-white window" fix (launcher + game)

A Linux player reported the game as an all-white xterm window. Root cause chain: the bundled WezTerm
failed to start on his system (typically missing libfuse2 or GUI libraries), the launcher fell back to
xterm with no color settings, and xterm defaults to black-on-white, which washes out the game's
dark-background palette. Fixed at three layers:

  • The launcher no longer strands or blinds you when WezTerm fails. play.sh now runs WezTerm
    supervised instead of replacing itself with it, so a crash at GUI startup falls through to a system
    terminal instead of leaving nothing (previously a WezTerm that probed OK but died on launch ended the
    session). When WezTerm cannot start at all, the launcher prints why and what to install (libfuse2
    hint), retries via AppImage self-extraction as before, and skips straight to the current terminal when
    there is no graphical display. Fallback terminal order now prefers desktop terminals (which follow the
    user's theme) with xterm last, and xterm is forced to white-on-black.
  • The game now paints its own dark background on Unix terminals. On an interactive Linux/macOS
    terminal the game sets the terminal's default background/foreground dark at startup (OSC 11/10, safely
    ignored by terminals that do not support it) and restores the player's colors on exit. Screen clears
    also erase in black as a fallback. This makes the game readable in ANY light-themed terminal -- a
    white xterm, macOS Terminal's default profile, or a fallback path -- regardless of the launcher.
    Windows, BBS door sessions (CP437), the MUD server/relay, and the Electron client are all deliberately
    untouched, and the bundled WezTerm keeps its own theme.
  • The accessible launcher's xterm fallback gets the same white-on-black flags.

Store-page accuracy (website + README)

Not part of the game binary, but shipped alongside this release: the website and README misstated
what the game contains. The class count now reads 12 (Mystic Shaman was missing), the companion count
reads 5 (Melodia was missing), and the Magician's spell claim was corrected (it has the 25-spell arcane
library, not "75 spells" which is a game-wide figure). Mystic Shaman and Melodia now have their own
cards on both the landing page and the Steam page, in all five site languages. The README version
reference was 4 releases stale and is updated, with a recent-highlights entry for the 0.62-0.65 arc.
(The in-game "Beta" banner is unchanged; that flip is a release-day action, not a 0.65.5 change.)

Documentation accuracy pass

The pre-1.0 audit found several docs describing retired architecture or wrong numbers; all corrected:
ARCHITECTURE.md now describes the current haproxy port-4000 topology (the old sslh diagram, the removed
usurper-world.service, and a stale Godot reference are gone); MULTIPLAYER_ARCHITECTURE.md is marked as
the historical per-process design with a pointer to the current model, and its password-hashing claim
corrected (PBKDF2, not bcrypt); SERVER_DEPLOYMENT.md points at the production topology as the source of
truth; BBS_DOOR_SETUP.md documents --screen-reader and --auto-provision, the automatic CP437
encoding, and clarifies the standalone world-sim's status; MODDING.md's data counts corrected (15
monster families, 79 achievements) and --export-discoveries documented; DOCKER.md's clone URL fixed;
and a new DOCS/LLM_CONFIG.md documents the optional USURPER_LLM_* sysop configuration for the first
time. CLAUDE.md's stale IP-ban security note was also clarified (account bans cover every path; only
SSH-path IP attribution is descoped).

Files Changed

  • Scripts/Core/GameConfig.cs -- Version 0.65.5
  • Scripts/Systems/CombatEngine.cs -- T0-3: boss...
Read more

Beta v0.65.4 Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 03 Jul 02:02

Release Notes - v0.65.4 (Countdown)

Sixth release of the Beta -> 1.0 "Countdown" cycle. A player-experience release aimed squarely at the two worst numbers in the live data: 25% of created characters never get a first kill, and 43% of all characters stall at levels 1-3. Two design passes (one on UI/onboarding, one on character progression) independently concluded the top-ROI fixes were about visibility, not new content: the onboarding guidance all stopped at the dungeon door, and progression rewards were invisible (the game never announced an ability unlock, never showed what was coming next, and never displayed XP progress). This release surfaces all of it, and adds targeted progression content (player specializations, martial ability fills, early class tuning).

"The First Kill": guide new players to their first fight and reward it

The single biggest funnel leak is the gap between creating a character and killing something. Every hint, banner, and journal prompt lived on Main Street; the moment a fresh character walked into the dungeon (entrance room, no monster, four unlabeled exits) they were on their own. Four changes close the loop so the whole progression cycle demonstrates itself in the first fight:

  • Guidance follows you into the dungeon. For a level-1 character with no kills, the room now prints one diegetic line pointing at the nearest fight: "A growl echoes from the east -- your first fight waits 2 rooms away." It uses the existing map pathfinding and only fires until the first kill.
  • The first kill guarantees level 2. Level 2 needs only a little XP; the lifetime-first kill now tops it up (stacking on the existing "First Blood" gold bonus), so kill number one always produces a level up.
  • Level-ups announce what you unlocked and what's next. New abilities used to appear silently in the quickbar. The level-up now prints "NEW ABILITY: Battle Cry" and "Next: Thundering Roar at level 20", on both the automatic level-up and the manual Level Master path.
  • A dungeon-hint wipe bug is fixed, and a new entered_dungeon telemetry event splits the funnel so we can tell whether lost players never entered the dungeon or entered and wandered.

The Path Ahead: progression made visible

The mid-game "desert" turned out to be class-specific: casters get a new spell every ~4 levels the whole game, while martials have 12-15 level ability droughts in the 28-60 band. The highest-value fix is making the ladder visible so every wait becomes a countdown:

  • New "The Path Ahead" screen (/path, /roadmap, and [P] Path at the Level Master): shows your level and XP to next, your next three ability/spell unlocks, and the next story gate (Old God / seal floor).
  • Ambient XP in the dungeon status line so every fight visibly counts toward the next level (Main Street already showed it).
  • Journal next-step at login: returning players used to land in the news feed with no reminder of what they were doing. After the "while you were gone" report, the game now shows the journal's next step (the pull-based /journal isn't discoverable to a returning player).

Player specializations (level 25)

NPC teammates have had 24 class specializations for a while; players never could. Now they can. From level 25, [S] Specialize at the Level Master lets you pick one of your class's two specializations (Arms/Protection, Retribution/Holy, and so on). A specialization adds additive stat growth on future level-ups and turns on the spec's combat passives (for example, Protection Warriors get +10% damage reduction on Shield Wall Formation plus the +20% shield-equipped ability damage NPC spec-tanks already had; healer specs get +20% healing output; Guardians get the +15% shield-equipped heal bonus). The first pick is free; changing it later costs a gold respec. Growth applies to future level-ups only -- choosing a spec doesn't retroactively re-grant past levels. Landing the choice at level 25 (the first Old God floor) frames it as "you survived the first god; choose who you are."

Both reviewers ran on this feature. save-state-reviewer: round-trip complete on single-player and online (the online save rides the same player blob), pre-0.65.4 saves load as unspecialized, no cross-player contamination, death-level-loss exactly reverses spec growth (apply/reverse symmetric). combat-reviewer: no double-application (single/multi path parity holds), None-spec players unaffected everywhere; its one finding (two shield spec bonuses still NPC-gated, leaving player spec-tanks weaker than NPC ones) was fixed in-release for full parity.

Progression tuning

  • Barbarian gets a level-1 active ("Wild Swing"). It was the only class with no level-1 ability -- its first was Rage at 5 and its first sustain not until level 36 -- and it died more than any class. Wild Swing pairs with the 0.65.3 innate heal-on-kill (kill faster, heal sooner).
  • Two Warrior ability fills close the worst martial droughts: Crushing Blow (level 34, fills 28-40) and Bulwark (level 62, a defensive stance, fills 55-70).
  • Bard's Song of Rest moves from level 12 to 18. Bard had a full party toolkit by level 14 while other classes were still finding their feet (0 Bard deaths in telemetry). This evens the early curve without touching Bard's signature abilities.
  • Training screen honesty. The training menu now shows what a point actually buys -- the effect multiplier, hit modifier, and (restored after being hidden in v0.49.9) the fizzle chance -- for the current and next proficiency tier, so the auto-attack-vs-abilities choice is legible instead of a hidden trap.

Smaller UI wins

  • Combat "?N" ability info: type ?3 in combat to see what quickbar slot 3 does (name + description) without spending your turn. The quickbar shows names and costs but never said what an ability does.
  • The next-step ticker now shows on BBS/compact terminals too (it previously lived only in the visual Main Street body, past the early return, so door-game and compact-mode players got no onboarding guidance).
  • The Settlement (The Outskirts) finally explains itself: a one-time first-visit note on what it is and that donations unlock services you can use, plus a line at the donation prompt showing what the currently-funded building will give you. (Player feedback: "why would I donate gold to something I don't understand?")
  • One fewer key-press wall in the new-character opening: the generic "Getting Started" screen no longer stacks in front of the Captain Aldric quest screen (which already says what to do) for new non-NG+ characters.

Companion quest chains: "The Long Road"

The largest content addition of the release, driven directly by player feedback: "the concept is cool but it feels empty... a whole lot of nothing until [the final quests] -- I'd rather see a full quest chain that starts much earlier and builds from there." Every recruitable companion (Aldric, Vex, Lyris, Mira, Melodia) now has a three-beat personal story chain: two new earlier episodes that fire in the dungeon and lead into their existing final quest (which is untouched -- it remains the payoff).

Each beat is a scene: some open with a real fight (a scavenger looting a King's Guard corpse, a corrupted temple healer, a thing that eats sound), all of them crack the companion's mask a little further, and each ends with a three-way choice in how you respond -- your answer shapes their loyalty and the tone of what follows. The beats reveal, ahead of the finals: the name of the demon Aldric won't say (Malachar), the truth about Vex's illness and the bucket list he's kept since he was twelve, what actually happened to Lyris's Deepwood (and why the last seed of the mother-tree stirs toward YOU), the doctrine that turned Veloura's healers into reapers before Mira's floor-40 reckoning, and the fact that Melodia's Lost Opus is not lost but unfinished -- a funeral she never got to complete.

Between beats, companions carry the story with them: new idle lines echo what happened ("Vex has the list out again, muttering about the spelling"), a nudge line telegraphs when the next episode is near, and a town notice fires so players who park a companion know their story moved. Beats can never be missed by out-leveling -- each fires on any floor at or past its band, in order -- and at most one beat plays per companion per dungeon visit so a catch-up player gets pacing, not a backlog dump.

Two structural fixes ride along: Vex's bucket list now unlocks the moment his second beat completes (it is, narratively, him deciding to start the list) instead of waiting for floor 70 or the unreliable day counter -- most players will now actually see his story; and beat rewards are small but real (loyalty, a one-time gold trick from Vex, healing herbs from Lyris, a full heal from Mira, a free War March from Melodia, and quarter-scale companion stat bumps mirroring the finals' pattern), all one-shot and unfarmable.

Design notes: beats deliberately stop firing once that companion's final quest completes (a setup scene after the payoff reads as a bug); Mira's second beat has a past-tense variant if you've already resolved Veloura; beat fights are ordinary level-appropriate monsters through real combat (fleeing simply defers the scene); nothing touches the sacrifice, grief, or romance systems. New save field PersonalQuestBeat round-trips through both companion save DTOs with a test locking the contract.

Deferred (documented, not this release)

  • Gear set bonuses (a new save-backed set-detection + bonus + display system) and faction-locked vendor gear: the design pass recommended deferring the set-bonus system until the mid-game progression work lands (sets mostly pay off at endgame, and 43% of players stall at level 1-3). Faction gear needs new balance-relevant item definitions and vendor wiring; rather than half-ship it into an ...
Read more

v0.65.3 Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 28 Jun 22:57

Release Notes - v0.65.3 (Countdown)

Fifth release of the Beta -> 1.0 "Countdown" cycle. The headline is two Power Strike fixes and a high-severity economy exploit. Power Strike: (1) the tactical [P] Power Attack was silently dropping every weapon enchantment, and (2) the Power Strike class ability did less damage than a plain auto-attack while wielding a two-handed weapon (the original report), because the basic attack got the two-handed damage bonus and the ability did not. The exploit (GitHub issue #112): unequipping reforged gear reset its quality and let players compound weapon power and gold into the billions. The release also includes the audit-driven shield/quality fixes and a broad consolidation of item conversion code to stop this class of bug recurring.

It also folds in a batch of fixes from a player's 15-hour playthrough notes: party XP silently stranded on a dead teammate's slot, the inn arm-wrestling daily limit not resetting when you sleep in single-player, no way to read or scroll merc contracts and a long quest list, no on-screen combat round counter or buff indicator (most glaring on a Bard), dungeon "[X] Examine" features so frequent they became autopilot busywork (now much rarer and worth more), companions/team NPCs not healing in town (resting now heals the whole party), and no way to inspect Black Market gear before buying (added).

Finally, a data-driven early-game survivability pass: a full-month read of live death data (after fixing a telemetry bug that was hiding most deaths) showed new-player permadeaths clustering at levels 1-3 and Barbarian as the deadliest class, so early floors now ramp instead of cliff and Barbarian gets a small innate heal-on-kill before its level-36 Bloodlust unlocks. No global nerf, no blanket dungeon change. Two smaller items from the same notes were already fixed in the working tree (the "look to redraw" prompt was dropped in v0.64.2; the two-handed Power Strike ability is the fix above) and the bed-upgrade label now names "fertility".

Power Attack now triggers weapon enchantments (life steal, elemental procs) like a normal swing

A player implied there was an unfixed Power Strike problem. An audit (with a combat-reviewer pass) confirmed one: the tactical [P] Power Attack was silently dropping every weapon enchantment. A normal attack runs your on-hit effects (life steal / Lifedrinker, fire/frost/lightning/poison/holy/shadow procs, the Sunforged holy bonus vs undead and demons, poison coating, and the Mystic Shaman weapon-enchant rider). Power Attack ran none of them, on either the main hit or the dual-wield off-hand follow-up. So spending 15 stamina on Power Attack actually gave up all of that proc damage and healing that a free basic swing would have triggered, which is exactly why it could feel weaker than it should. (The "Power Strike" class ability on the quickbar was unaffected; it already procced enchants. This was specifically the [P] tactical action.)

The cause was a fix that had only ever landed on the basic-attack path (and an unused legacy code path), never on the live Power Attack. Fix: the entire on-hit effect pipeline is now a single shared routine that the basic attack and the Power Attack (both hits) call, so the two can never drift apart again. As part of the same fix, the Power Attack now also correctly skips its on-hit procs when a swing is evaded (passes harmlessly through an Incorporeal/Phase-type enemy), matching the basic attack. Build clean, full test suite (845 tests) passing, combat-reviewer verified.

This also explains an older report of a "Power Strike bug with two-handed weapons." A two-handed wielder never makes a dual-wield off-hand swing, so their whole Power Attack is the single main-hand hit, and that hit was the one dropping every enchant. Two-handed weapons also tend to carry the biggest single enchantments (a life-steal greatsword, an elemental maul), so the lost procs were most glaring there. With this fix, a two-handed Power Attack procs its enchantments just like a normal swing. (Separately confirmed working and unchanged: the two-handed damage bonus still applies to Power Attack, and a two-handed weapon never triggers a phantom off-hand strike.)

Power Strike (the class ability) no longer does less damage than a basic attack with a two-handed weapon

The original report: while wielding a two-handed weapon, the Power Strike ability dealt less damage than a normal auto-attack, even with training points specced into it, to the point that the player pulled their points back out of Power Strike and into auto-attack. (This is the trainable class ability, distinct from the tactical [P] Power Attack covered above.)

Cause: two-handed weapons get a flat damage bonus per swing to compensate for having no off-hand or shield. A normal attack applied that bonus, but the class ability's damage was computed without it, and the ability also draws a smaller share of raw weapon power than a normal swing. For a two-handed build that combination meant a free swing could out-damage the ability, which is backwards: an ability that costs stamina and a cooldown should be worth using. Fix: physical weapon-strike abilities now get the same two-handed bonus a normal attack does, so Power Strike (and other physical melee/ranged abilities) reliably beats a basic swing for two-handed builds, at every level. Magical abilities (holy smite, lightning bolt, and the like) are excluded because they scale off the caster's stats, not weapon power. The two-handed bonus value is now a single shared constant used by both the basic attack and the ability path. Applied consistently to single-target, multi-monster, and player-versus-player combat. A combat-reviewer pass confirmed the magnitude is proportionate (it matches what the basic attack already receives) and does not reintroduce the endgame one-shot risk an earlier balance pass addressed.

Unequipping reforged gear no longer resets its quality (and the reforge gold/power exploit it enabled)

A player reported (issue #112) that unequipping a reforged piece of gear reset its quality all the way back down to normal while keeping its stats, and that this enabled two exploits: repeatedly reforging a weapon, unequipping it, and reforging again would compound its weapon power past a billion, and the gear's gold value grew exponentially along with it, letting a low-level character make billions of gold cheaply. A follow-up noted a related problem: unequipping certain items also dropped stats outright (a shield lost its Constitution and block; another item lost its agility).

Both problems shared one root cause. The equipped form of an item (which tracks its quality/rarity and its full stat list) was being converted to a plain backpack item when unequipped, and that conversion was lossy. Rarity had nowhere to live on a backpack item, so re-equipping rebuilt the item as the lowest quality. Reforging gives a chance to upgrade rarity, and each upgrade grants a flat boost to all stats including weapon power, but only while the item is below the top rarity. Because unequipping reset the rarity, the player could re-collect that upgrade boost on every reforge-unequip-reforge cycle, compounding power and value without limit. Separately, two of the unequip paths used a hand-written conversion that only copied a subset of stats, so agility, charisma, stamina, block chance, the minimum-level requirement, and the entire list of bonus effects (constitution, intelligence, every enchantment and proc) were dropped on unequip.

Fix: items now carry their rarity, so it survives the unequip and re-equip round trip (and saving, loading, the home chest, trades, and being handed to a companion). With rarity preserved, a maxed-out item correctly stops qualifying for the reforge upgrade boost, so power and value no longer compound, which closes both exploit paths. The lossy unequip conversions were replaced with the complete one, so unequipping no longer drops any stat. The item-quality display also now honors the real stored rarity instead of guessing from the item's name and power, so reforged quality reads correctly in the backpack. Note: an item whose value was already inflated by this exploit in an earlier save is not retroactively reduced; the fix stops any further inflation. A save-state-review confirmed the rarity field round-trips on every save path (single-player and online, player inventory, home chest, and companion/NPC bags) and that older saves load safely (an item with no stored rarity loads as normal quality, and its displayed quality still falls back to the name/power heuristic). Two new round-trip tests lock the contract; full suite (847 tests) passing.

Audit follow-up: shields keep their block value on re-equip, and reforged quality survives every equip path

A comprehensive audit of the issue #112 fix surfaced a related defect the original fix exposed. A shield stores its block value in a dedicated field (separate from regular armor), but several "put an item back on" code paths were reading the wrong field to restore it -- they read the regular-armor value, which is zero for a properly-formed shield. The old unequip code happened to mask this by stuffing the block value into the regular-armor field; routing unequip through the complete converter (the issue #112 fix) made the mismatch visible, so a shield could come back with no block value after being taken off and put back on. The same family of code paths also reset an item's quality to normal when re-equipping it from certain screens (the player's inventory, the home equip screen, the magic shop, and a shared equip helper), which was both a cosmetic quality reset and a second way to re-arm the reforge exploit (equip a reforged weapon through one of those screens and its quality dropped, re-enabling the upgrade boost).

Fix: every place that puts an item back on now reads the shield's block value from the ...

Read more

Beta Version 0.65.2 - Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 26 Jun 06:57

Release Notes - v0.65.2 (Countdown)

Third release of the Beta -> 1.0 "Countdown" cycle. It bundles two batches of work. First, a detailed co-op (leader/follower) play-session feedback report: a pair of players ran a leader (DPS) plus follower (healer) session and wrote up five issues -- four fixed (echo recruiting, the follower combat HUD, the username leaks, the follower loot-claim input, and the end-of-fight text ordering), and one (the apparent loot "duplication") investigated and confirmed to be a display-only non-bug, with its messaging clarified so it no longer reads that way. Second, a batch of targeted fixes: five from GitHub bug reports (issues #103, #108, #109, #110, and #111), seven from in-game bug reports (an untranslated dungeon event, a companion potion-stash bug, life steal triggering on an evaded attack, auto-level-up not respecting the off setting for multiplayer followers, dungeon rest spots not healing the party, off-hand strike damage differing between skills and normal attacks, and shop staves being one-handed while loot staves were two-handed), and a server-stability and log-noise pass driven by server-monitor alerts (one real session-ending crash plus several false-alarm error logs).

Player echoes failed to recruit after a character took a family surname (regression)

A player reported that a specific character's "echo" could no longer be recruited at Team Corner: recruiting it produced "save data cannot be found," and the failure persisted across logouts and saves.

Root cause: a v0.65.1 regression from the new family-surname feature. The echo recruit stores the recruited character's DISPLAY name in the party list, but the echo loader looks the save up by account USERNAME. Before v0.65.1 those usually matched; v0.65.1 made DisplayName include the family surname (e.g. display "Imperius Ashwick" vs account "imperius"), so any surnamed character's echo now failed to load, and the stuck name lingered in the recruit list (so Team Corner kept refusing a re-recruit). Fix: when the direct username lookup misses, the echo loader now resolves the stored display name to the canonical username (via the existing ResolvePlayerUsername helper, which matches on username OR display_name) and retries. This is robust to any display-vs-username divergence, not just surnames, and it self-heals echoes that were already stuck from earlier sessions (the next dungeon entry loads them correctly). Not leader/follower-specific; affects all echo recruiting.

Follower combat HUD now shows the leader's (and the whole party's) HP

A follower healer reported that during combat they could not see the leader's health on the combat status screen; the only place the leader's HP appeared was a small text line printed after each full round.

Cause: the in-combat status screen a follower sees lists the monsters, the follower's own stats, and the follower's ally list, but the leader is the combat owner and is deliberately not in that ally list, so the leader was never drawn. (The heal-target list added in v0.65.1 does include the leader, but only appears when heal options are available.) Fix: the follower's combat HUD now prints a compact, persistent party-HP line every turn, including the leader plus every teammate and the follower, color-coded by health percentage (and showing DOWN for the fallen), so a healer always sees who needs attention. Display-only; the leader's view and single-player are untouched.

Account usernames no longer shown in place of character names

A player noted (as a privacy concern) that their account login name appeared publicly in several spots instead of their character name, which matters because some people use a real name or email as a login.

Fixed the player-facing leaks:

  • The follower's in-dungeon footer now reads "Following " instead of the leader's account name.
  • The realm-wide logout broadcast ("X has left the realm") now uses the character's display name.
  • The dungeon-leave notices (a follower leaving, and the leader ending the run) now use the character's display name.

(The group-leave notice and the Discord departure relay already used the character name, so they were already correct.) A remaining minor edge: /who can still show the raw login name for a connection that has authenticated but not yet picked a character; that case has no character name to show and is queued for a placeholder pass.

Follower loot claims are no longer intermittently "not recognized" (Topic 3B)

A follower reported that claiming an offered drop worked only sometimes; other times their keypress was "not recognized" and the loot was passed on.

Root cause: the follower's combat input travels over a one-slot channel, and the engine toggled "I am waiting for input" on and off per prompt. A leftover keypress (for example, from rapidly answering one prompt while the next was being prepared) could sit in that one slot and be consumed first by the next read, shifting every subsequent read by one. So the follower's loot "take" was read as the stale leftover (invalid -> auto-pass), and their real keypress fed the following prompt. Fix: the engine now drains any stale buffered input immediately before each follower combat read (both the action menu and the loot prompt), so every read matches the prompt the follower is actually looking at. A combat-reviewer pass confirmed this only ever discards genuinely stale input (the follower's real response cannot have arrived yet), and as a bonus it clears a latent full-channel stall the old behavior could produce.

Co-op combat text now renders in order at the end of a fight (Topic 2)

Players reported that toward the end of a battle, follower action lines could appear below the boss-kill summary, out of sequence.

Root cause: the leader receives a grouped player's action lines through an incoming-message queue that is only flushed when the leader next reads input, but the boss-kill summary renders before that next read -- so the final round's follower actions printed after the summary. Separately, followers never saw the boss-kill summary at all (it was drawn only on the leader's screen). Fixes: the leader now flushes any queued co-op messages right before drawing the boss-kill summary, so the final follower actions appear above it in order; and a compact summary line is now broadcast to grouped followers so they see the outcome too (it arrives after the action lines in their stream, in order). The mechanical outcome (kill order, rewards) was always correct; this is purely display ordering. A combat-reviewer pass confirmed the flush has exclusive access to the leader's stream (no race), the follower broadcast cannot double-display on the leader, and both are no-ops in single-player.

Loot "duplication" (Topic 3A) was never a bug -- but the messaging is now clearer

Investigated and confirmed there was no real duplication: when a follower wins a roll, the item is added to exactly one inventory (the follower's), and gold is split across the party. The confusion was on screen -- the leader sees the full item render (it goes to whoever has first claim) under an "ITEM FOUND!" banner, so it read like the leader's own find, and the same details were broadcast to the other party members with no label. The messaging now makes ownership explicit: when a drop belongs to a follower, the leader sees a clear "[Group loot] {name} has first claim on this drop. You receive it only if they pass it to you." line up front; the recipient follower sees "This drop is yours to claim."; and the broadcast to the rest of the party is labeled "[Group loot] {name} found a drop:" so nobody mistakes it for their own. Display-only; the actual grant and gold split are unchanged. 3 new loc keys in all 5 languages.

Loot can no longer be handed to a player echo (where it would be lost)

A player reported that when a dropped item is passed on and an ally is offered the chance to pick it up, a recruited player "echo" could be chosen as the recipient. Echoes are read-only snapshots loaded from the real character's save, so their equipment cannot be persisted: anything equipped onto an echo during the run is silently lost when the echo reloads. The reporter rightly asked that echoes not be eligible loot recipients.

Root cause: the post-combat loot flow had two places that treated an echo like any other teammate. (1) The automatic ally pick-up that fires when you Pass on a drop evaluated echoes as candidates (it already skipped live grouped players and royal-bodyguard mercenaries, but not echoes), so it would offer the item to an echo. (2) The loot-drop screen's </> "Change character" recipient cycler built its list from the player plus every teammate, so you could also manually cycle to an echo and Equip or Take the item onto it. Either route ended with the item placed on a character whose gear is never saved, so it vanished.

Fix: echoes are now excluded from both loot paths. The automatic ally pick-up skips echoes the same way it already skips grouped players and mercenaries, and the </> recipient cycler no longer includes echoes, so it can never land on one. This matches the existing party-equipment manager, which already refuses to manage an echo's gear. Legitimate recipients are unchanged: companions, Team Corner NPCs, spouses/lovers, and live grouped players remain eligible exactly as before. Display-only ally offers (round-robin loot among live grouped players) were already echo-safe and are untouched. No new strings; this is a pure eligibility fix.

A combat-reviewer pass confirmed both the single-monster and multi-monster victory paths route through the one fixed pick-up method, that the cycler's recipient index stays valid after the echoes are filtered out, and that no other loot path (the round-robin offer, the grouped-follower equip/take, the pass-down cascade, or the take/equip handlers) can st...

Read more

Beta v0.65.1 Release Notes

Choose a tag to compare

@binary-knight binary-knight released this 23 Jun 19:02

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 k...

Read more

Beta v0.65.0

Choose a tag to compare

@binary-knight binary-knight released this 22 Jun 07:10

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 c...
Read more

Beta v0.64.1 - Patch

Choose a tag to compare

@binary-knight binary-knight released this 10 Jun 06:10

Release Notes - v0.64.1 (The Brain)

Two changes on top of v0.64.0.

Pre-release audit pass (5 parallel reviewers, ~25 findings fixed)

Before cutting this release, the full v0.64.1 changeset was audited by the project's four specialized reviewers (combat-reviewer, npc-system-reviewer, relationship-system-reviewer, save-state-reviewer) plus a general pass over the LLM layer and localization. The fixes, grouped by severity:

Release blockers fixed:

  • OpponentSpared punished as a loss at 6 PvP call sites. Every pre-existing caller treated != Victory as defeat: sparing a pit-fight opponent cost 20% of carried gold + a recorded loss; sparing the last gang-war enemy forfeited the entire war with a public defeat broadcast; sparing the beaten king printed "YOU WERE DEFEATED." New allowSurrender parameter on PlayerVsPlayer suppresses the surrender prompt in staged-fight contexts (pit fights, gang wars, throne challenges, bounty hunts); the open-world sites that keep surrender (dormitory/inn sleeping attacks) gained explicit OpponentSpared outcome branches so the spare narration isn't followed by a contradictory "they fought you off" line.
  • Real teammate combat deaths were being swallowed. CombatEngine.HandleNpcTeammateDeath deliberately bypasses the IsPlayerTeam protection before calling MarkNPCDead, but not the IsInConversation protection -- and this release guarantees every dungeon-party NPC carries that flag. The guard healed the legitimately-killed teammate to MaxHP/4 and skipped the entire death cascade, while CombatEngine independently set IsDead=true: an inverse zombie. The engaged flag is now suspended around the call exactly like Team.
  • The spouse-death notification was inverted. It ran AFTER HandleSpouseBereavement cleared the deceased's marriage flags (so its own gate made it dead code on every permanent death), and it ran unconditionally after the temp-death respawn branch (so players got "your spouse has died" mail for spouses who respawn in ~10 minutes). Now fires before bereavement inside the permadeath branch + the aging path only, and skips NPC-NPC marriages (spouse name resolving to any pool NPC) so name collisions with player display names can't trigger false widow mail.
  • The avenge-loop fix needed a third pass. The audit found the AddGoal completed-goal dedup was structurally hollow (UpdateGoals prunes completed goals at the top of EVERY tick, not just at the MaxGoals cap) and the per-instance throttle HashSet reset on every online world reload (minutes apart on a busy server), not just restart. Replaced with a process-wide static SettledRevengeTargets registry keyed owner|target, consulted at BOTH goal-add time (stops the loop at the source) and the news-fire seam. A revenge goal that would be born-complete (target confirmed dead in the pool) is refused outright. The LLM strategic-goal callback also no longer mutates the goals list from its background thread -- it enqueues to a ConcurrentQueue drained on the tick thread (a swallowed-exception/list-corruption race at ~244 NPCs x 4 refreshes/day).
  • Goal.TargetCharacter now round-trips. It was never serialized (harmless decoration in v0.64.0; load-bearing in v0.64.1 for Slices 13/14b/19/20 + revenge completion detection). A restored in-flight Avenge goal had a null target -- a zombie that could never complete and silently killed the target-keyed features after any reload. Added to GoalData + both writers + both restore sites; additive, pre-existing saves default to empty.
  • Slice 20's player-self guard was dead code, and quest claim failures leaked. The guard compared Character.ID values that are never populated for players, so an NPC whose Avenge goal targets the PLAYER would offer a kill quest against a same-named NPC bystander. Replaced with name matching against the player's identities, applied in both the option builder and the handler. Separately, ClaimQuest's result was ignored: a refusal (level too low, daily cap, royals) still printed "accepted!" and left an unclaimed, claimable-by-anyone quest in the shared database. The result is now checked, the quest is restricted via OfferedTo as defense in depth, and refusals show an honest failure beat.
  • LLM caches were not thread-safe. All six new per-NPC caches were plain Dictionary<string,string> on shared NPC objects -- two sessions conversing with the same NPC concurrently could corrupt the dictionary, and the cache reads sat OUTSIDE the try/catch so the exception would propagate into the conversation flow instead of falling back to the template. All six converted to ConcurrentDictionary, initialized at declaration.

Should-fix issues fixed:

  • IsInConversation releases now also clear the LIVE ActiveNPCs object (v0.57.2 orphan pattern) at the dungeon-exit finally and both mid-dungeon remove sites; the sync re-assert flags the live twin too. Pre-fix, a mid-dungeon world reload left the live object permanently protected (immortal + un-interactable) because every release path wrote only to the party's orphaned reference -- and the new reload-preservation would then re-assert the leak forever.
  • Target steering now routes through NPC.UpdateLocation (the raw field write skipped the LocationManager presence registry, accumulating ghost entries per steer), and refuses to steer NPCs who are engaged with a player or themselves in the Dungeon.
  • The online quest writer (SerializeCurrentQuests) now copies TargetNPCName -- without it, the world_state copy of DefeatNPC bounties couldn't track its target across a server restart.
  • The reaction-style LLM generators (romance reactions, news comments, NPC flirts, quest requests) had reused the goal-greeting system prompt, whose "the player is the named target of your strategic goal... a threat, a plea" framing was factually wrong for a compliment-thanks and directly contradicted the quest-request user prompt. New neutral SystemPromptSpokenLine.
  • The Slice 19 hail was silently dead for any online player whose account name differs from display name: the greeting cache was written under a Name1-first key but read back Name2-first. Keys aligned.
  • SanitizeLLMOutput's hardcoded 300-char cap silently overrode the 500/600-char caps the topic-response and quest-request callers applied afterwards (mid-sentence truncation at 297 chars). The cap is now a parameter.
  • Four pre-existing flirtCountThisSession double-counts removed (married-refusal, taken-refusal, and both affair branches incremented in-handler on top of the caller's increment, burning 2 of the 5-per-session flirt budget each).
  • NPC-initiated flirts now skip asexual players, skip NPCs whose SpouseName is the player (id-drift name fallback, v0.57.7 pattern), and married low-Commitment initiators get an honest "you ARE married, but your commitment wavers" prompt instead of speaking as singles.
  • The Spare/Finish prompt accepts localized affirmatives (Y/S/O/I per the v0.57.7 precedent) so a Hungarian "I" (igen) no longer executes the NPC the player meant to spare; the spared NPC's combat statuses + persistent poison are scrubbed and they heal to MaxHP/4 (a leaked DoT was a delayed, unattributed kill that silently undid the mercy).
  • The affair-fork decline choice text told the LLM "the affair ends here" while the code keeps the affair alive -- text matched to mechanics.
  • Slice 15's bounty news no longer claims the NPC "claimed the bounty" (it stays on the player board by design); "Quest Hall" added to the NPC location mapper; one em-dash in a debug log replaced per project convention.

Second should-fix pass (remaining audit items):

  • TTL on the engaged-NPC reload preservation. A hard crash mid-dungeon (no finally) can leave IsInConversation stuck true, and the new reload-preservation would have immortalized the leak. The preservation now tracks when each name was FIRST seen engaged and stops carrying the protection across reloads after 2 hours -- far longer than any dungeon run, short enough that a leaked flag self-heals.
  • Confession at the lover cap is no longer a permanent dead end. Pre-fix, an accepted confession that hit MaxConcurrentLovers still set HasConfessed + ConfessionAccepted with no Lover record and no retry path (the Confess option gates on not-yet-confessed) -- even after the player freed a slot, that NPC could never be formalized. Now a cap rejection doesn't consume the confession: neither flag is written, and the player can free a slot and confess again.
  • No more false public revenge claims against players. A revenge goal whose target never appears in the NPC pool (player killers, transient hostiles) auto-completes by design -- but the celebration broadcast "X took blood for blood from Y" was a false, public claim against a possibly-living player the NPC never touched. Celebrations now fire only when the target is CONFIRMED dead in the pool; unconfirmed targets complete silently (goal closes, internal catharsis fires, no news).
  • Flirt-success reactions respect warmth tiers. The LLM cache bucket is now success_mild / success_medium / success_strong -- pre-fix a single "success" bucket meant the first landed flirt's cached line replayed verbatim for later, deeper successes.
  • Seven hardcoded English romance lines loc-keyed in all 5 languages: the married/taken flirt refusal fallbacks (the ONLY lines non-LLM sessions ever see) and five affair-conversation narration beats (tearful "I choose you", hand-squeeze, pulls-away, hesitates, looks-away).
  • Daily cap on spare-mercy alignment farming. New SparesToday counter (full MurdersToday-style save plumbing) caps the +10 paired-good alignment reward at MaxAlignedSparesPerDay = 3; sparing itself is never blocked, only the alignment pump stops -- consistent with every other alignment lever (murders 3/day, tribute 3/day, Love Street 2/day).
  • **Slice 11's dialogue-fla...
Read more

Beta v0.64.0 - The Brain

Choose a tag to compare

@binary-knight binary-knight released this 30 May 09:17

v0.64.0 -- Brain v2

TL;DR

  • NPCs in the world simulator finally have agency. They look at their goals, their personality, their recent memories, their gear, and what happened to them last time they did this exact thing -- and they pick the next action based on all of that. The weighted-random Markov picker that has been driving 250 NPCs since the BBS port is replaced.
  • The world stops being a meat grinder. Two weeks of telemetry showed NPCs were dying at 70 times the rate they were progressing, spawning into the dungeon with no gear, and burning their pittance of gold on inn drinks before they could ever afford a sword. The economy is structurally reworked so NPCs actually get somewhere.
  • High-stakes NPCs (kings, your teammates, anyone at level 30 or higher, court members) now fight real combat. They cast spells. They drink potions. They cleave with AoE abilities. They taunt monsters. They die to status effects you watched the player learn to fear three patches ago.
  • LLM moments. Optional. Online-only. Server-key. Daily token cap. Heuristic fallback always works so the game runs identically when no API key is configured. When the LLM IS configured, it writes the dramatic news flash when an NPC kills the one who killed their parent, the eulogy when a notable NPC falls, the first-impression flavor when you examine someone at Team Corner, the personalized mood prefix and memory callback and witness allusion and faction-tension line that wraps every line of NPC dialogue, the long-arc strategic goal stack that shapes an NPC's behavior across thousands of subsequent ticks, and the accept/hesitate/refuse decision when you propose marriage.
  • A live LLM telemetry dashboard at https://usurper-reborn.net/balance.html tracks every call: success rate, token spend, cost in dollars, what each NPC actually said. Sysops can watch the LLM activity in real time and tune from the data.
  • Net effect for the player: the simulated world feels less like a screensaver and more like a place where people are doing things for reasons. The NPCs around you are progressing, dying for real reasons, gossipping in their own voices, holding grudges that lead somewhere, and occasionally making decisions that surprise you.

What it means at the table

For a long time the world simulator has been a beautifully detailed Rube Goldberg machine that mostly produced nothing. NPCs had personalities, relationships, memory systems, emotional states, a goal stack. They had jobs to do. They had places to go. And then, every tick, a weighted-random number generator picked one of seventeen verbs and the NPC went and did that thing regardless of anything else. They spawned naked, they fought monsters they couldn't damage, they got drained by inn tabs they couldn't afford, they died.

Walk into the dungeon now and the NPCs you bump into are actually advancing. The Lv 14 fighter you saw last week is Lv 16 now because she's been winning her team dungeons instead of dying in them. Her wounds heal at the healer for a sane price. Her share of the gambling pot at the Dark Alley pays off two times out of three because she has the Wisdom for it. She bought a sword. She's saving for armor.

Walk past the Inn and a sociable Bard is running a forty-gold tab instead of getting bankrupt at a hundred. Three tables over a brawl is breaking out because two NPCs with bad blood between them ended up at the same bar. Tomorrow the news feed will mention it.

Engage one of them in conversation and the line they greet you with is colored by their current mood. Scowling, they say... if they're angry. Eyes alight, they say... if they're proud. If you bailed them out of trouble last week the line that follows mentions you "haven't forgotten" what you did for them. If you killed their cousin in a back alley and they witnessed it, the line that follows alludes to it sideways. None of it spells out the event. The NPC just sounds like someone who remembers.

Propose to them and the response isn't a die roll against personality stats. It's a decision, made by an actor that read your full relationship history, your charisma modifier, the heuristic acceptance chance, and their own personality, then picked accept or hesitate or refuse on character grounds. Sometimes they'll surprise you.

Examine a notable NPC at Team Corner and the first-impression paragraph at the top reads like something a stranger would say after spending five minutes with them. "Korr Stonewarden carries himself like a man who has counted out coin in every shop in the city and is still not done counting. His hands are merchant's hands; his eyes are not." This is generated once per NPC and cached for the next examine.

Die in the dungeon while pursuing an Old God and the news feed will sometimes carry a eulogy that talks about who you were, what you were doing, and who put the blade in. Not always. Not for every commoner. But for named characters and people who mattered.

None of this is the game's narrative spine. The game still plays the same way. But the world around you is noisier, more specific, more alive.


What it means under the hood

For the sysop running the live server: this release introduces an optional dependency on an external LLM API (Anthropic, OpenAI, OpenRouter, or any OpenAI-compatible endpoint -- the existing http chat-completions provider works with all of them). The dependency is opt-in via environment variables. Without a key configured, every LLM code path falls through to its heuristic template and the game runs identically to the pre-Brain-v2 baseline.

When configured, the cost envelope at a 250-NPC population with a moderately active player base lands at roughly $1-3 per day on Sonnet 4.6, with a per-server daily token cap (default 500,000 tokens) that hard-blocks runaway spending. Every LLM call writes to a new llm_usage SQLite table -- one row per attempt with moment type, NPC name, success/failure, token counts, latency, rendered text, failure reason -- and the existing balance dashboard gets a new LLM Moments tab that reads it. You can see every call in real time, what it cost, what it produced, what failed and why.

The deploy step adds one systemd drop-in (/etc/systemd/system/usurper-mud.service.d/llm.conf) with five Environment= directives. The game restart picks it up and the LLM starts firing within seconds. Killing the file and restarting the service turns it off. No code change, no rebuild, no save migration.

The Brain v2 work itself is rollout-safe. A new IsAIDriven flag on Character defaults to false; pre-v0.64.0 NPCs in the save deserialize with it false and continue running through the legacy weighted-random picker exactly as before. New arrivals (immigrants, child graduates, orphans aging out) spawn with it true and run through the new utility scorer. The cohort grows organically over weeks as the population turns over. Telemetry on every NPC decision logs the cohort tag so dashboard pivots can compare them directly.

One caveat worth knowing: the LLM strategic-goal layer (Slice 12a) originally gated on the same IsAIDriven cohort flag, but a live audit after first deploy showed the cohort had stayed at zero NPCs across 244 in the population (organic turnover hadn't fired the creation paths). The gate was dropped for strategic goals specifically, since that layer is additive (it augments the existing reactive goal stack rather than replacing the picker) and the cohort split was only meaningful for picker comparability. A startup stagger spreads first-time refreshes across six hours so a restart doesn't fire 244 simultaneous HTTP calls.

The combat engine players use is completely untouched. All Brain v2 combat code is a parallel headless module that runs without terminal I/O, without GMCP emits, without broadcast plumbing. The two paths share nothing structurally.


How this release got the way it is

This release rolls two bodies of work together. The v0.63.2 release notes were drafted, the code was built and tested, and then it stayed on the shelf instead of going to the live server -- because the architectural problem the v0.63.2 telemetry pass uncovered was bigger than the tuning fixes could solve, and starting Brain v2 became the right call. So the v0.63.2 fixes ship inside v0.64.0 as the foundational layer that makes the Brain v2 layer's job possible. The two halves are not independent; they were always going to ship together.


Part 1: Making the NPC economy actually work

Fourteen days of telemetry from the live server before this release: 283,117 NPC decisions, 9,635 deaths, 138 level-ups. The death-to-progression ratio was seventy to one. Seventy-one thousand of those actions were performed by NPCs stuck at exactly Level 50 (the immigrant default) and never advancing. The team-dungeon path was a meat grinder where over a third of attempts ended in death and less than a sixth of one percent ended in a win. Dark Alley and Inn together took up nearly forty percent of all NPC time and produced average gold deltas near zero. The whole simulation was running, every system was firing, and almost nothing was happening.

A handful of structurally-load-bearing problems turned out to be doing most of the damage.

The math wasn't working

At the live server's NPC XP multiplier of 3.0, a successful team-dungeon attempt was worth about fourteen XP. The next level at Level 50 needs about a hundred and fifty thousand XP. A maximally-active NPC running two hundred team dungeons in two weeks at fourteen XP per attempt accumulates twenty-eight hundred XP -- less than two percent of what they need to level up once. Even if every single attempt was a win, NPCs above Level 30 mathematically could not progress on the timescale of weeks.

The fix is unromantic: the default NPC XP multiplier is bumped from 1.0 (single-player) and 0.25 (BBS) to 5.0 across both code pa...

Read more

Beta v0.63.1 - Hotfix

Choose a tag to compare

@binary-knight binary-knight released this 29 May 07:58

v0.63.1 -- Bloodlines hotfix

A follow-up to the v0.63.0 ship. One menu-visibility bug, two half-shipped features that turned out to have no downstream consumers, and the explanation for one design-by-omission that read as a bug.

Bloodlines of the Realm was invisible from the Castle menu

The [Z] Bloodlines of the Realm action shipped with v0.63.0 worked correctly when pressed, but it was never actually rendered in any of the Castle menu displays. So even though the case handler existed and the family-tree screen was fully functional, no one could tell the option was there.

Now fixed across all four Castle display variants: the visual exterior screen commoners see, the visual interior screen the king sees, the BBS-compact exterior, and the BBS-compact interior. The commoner hotkey, which had landed on F while the king's was on Z, is now Z in both flows so muscle memory is consistent regardless of whether you're on the throne.

The king's [Y] Sponsor Heir to Court action was also missing from the BBS-compact king display while present in the visual one. That row now shows both [Y] Sponsor Heir and [Z] Bloodlines together.

New localized menu label: "Bloodlines of the Realm" across English, Spanish, French, Hungarian, and Italian.

Dynasty standing now does something

The v0.63.0 release notes promised each Dynasty tier grants a reaction bonus (+5% Founder, +10% Patriarch / Matriarch, +15% Bloodline, +20% Dynasty). The function that computes the bonus was implemented and the standing tier shows up correctly on the [%] Status display, but it turned out the function had zero callers anywhere in the codebase. The bonus existed on paper and nowhere else.

Now wired into AlignmentSystem.ApplyVeloraReactionBonus, the canonical reaction-modifier pipeline. The Dynasty bonus stacks multiplicatively alongside Veloura shrine attunement and Cave Spider pet penalties, applied on top of the alignment-compatibility base modifier. A Patriarch / Matriarch interacting with an aligned good NPC now sees a 65% reaction bonus instead of 50%; a Dynasty-tier player sees 80%.

Family-grudge NPCs now actually react to you

The v0.63.0 release notes promised that an NPC whose parent, sibling, or child the player killed would refuse to talk, refuse to join the player's team, and attack on sight if dark-aligned. The memory was being written into every relative's memory store on the death cascade, and the killer's name was going into their Enemies list, but a code-wide grep for downstream consumers of either signal returned zero hits. The "multi-generational rivalries" feature was a write-only pipeline.

Now wired with three consumers:

Refuses to talk. When you press [T] Talk on an NPC who has lost a family member to you, the conversation refuses to open: "{name}'s eyes go hard at the sight of you. They turn their back and walk away." followed by "Some blood debts do not forget." This runs after the adult-child recognition check (so your own kids don't refuse you, even if they nominally have grudges) and before the Dread flee check. Story NPCs and the king are exempt to keep quest flow unblocked.

Refuses to join your team. The Team Corner recruit list now sends any grudge-bearing NPC straight into the Refused band regardless of their friendship score, so they show up in the candidate list with a "won't join" tag instead of an unrealistic recruitable cost.

Attacks on sight if dark-aligned. Street encounters now scan the living NPC roster for any dark-aligned grudge-bearer in your level range, and route them in as the hostile NPC encounter ahead of the random-evil-thug pool. When they appear, their hostile phrase is replaced with a family-revenge line: "You killed my blood. Now I take yours." / "My family does not forget. Neither do I." The fight uses normal CombatEngine, so if you kill the avenger you've permadied them like any other NPC and their relatives now hate you too.

A new helper FamilySystem.HasGrudgeAgainst(viewer, killer) is the single source of truth for the grudge check. It reads the KilledMyParent and KilledMyFamily memory types directly rather than the Enemies list, which sidesteps an inconsistency where everything else in the codebase stores NPC IDs in Enemies while the family-death writes were storing display names.

Five new revenge-phrase loc keys per language plus two grudge-refusal lines, all translated into Spanish, French, Hungarian, and Italian.

Dynasty standing is empty until you actually have a grown child

Not a bug, but worth calling out because someone asked: the Dynasty standing line on [%] Status only appears once you have at least one living adult child. If you haven't raised a kid to 18 yet, the line correctly does not render. This matches the Sellsword Hall standing, which also only appears once you've completed at least one mercenary contract. The release notes called this out, but it's easy to read as a bug when the line you expected to see is missing.

For context: a child is born at intimacy scenes (Home, Love Corner, etc.), grows up over in-game time, and graduates at 18 to become an adult NPC. Once that happens, the Dynasty line appears and the reaction bonus starts applying.

Files changed

  • Scripts/Core/GameConfig.cs -- version bump.
  • Scripts/Locations/CastleLocation.cs -- four display blocks updated, commoner case handler hotkey changed from F to Z.
  • Scripts/Systems/AlignmentSystem.cs -- Dynasty reaction bonus folded into ApplyVeloraReactionBonus.
  • Scripts/Systems/FamilySystem.cs -- new HasGrudgeAgainst helper.
  • Scripts/Locations/BaseLocation.cs -- grudge gate added to InteractWithNPC between recognition and Dread checks.
  • Scripts/Systems/TeamSystem.cs -- grudge gate added to GetRecruitmentBand.
  • Scripts/Systems/StreetEncounterSystem.cs -- grudge-revenge priority scan in FindHostileNPC, family-revenge phrase pool in GetHostilePhrase.
  • Localization/en.json / es.json / fr.json / hu.json / it.json -- 8 new keys (1 Bloodlines menu label + 2 grudge-refusal lines + 5 revenge phrases).