Skip to content

v1.1.13: Main Street districts, playtest fixes, royal court and shared-record saves - #154

Merged
binary-knight merged 46 commits into
mainfrom
release-1.1.13
Sep 25, 2026
Merged

binary-knight merged 46 commits into
mainfrom
release-1.1.13

Conversation

@binary-knight

Copy link
Copy Markdown
Owner

Version 1.1.13. Release notes: DOCS/release-notes/RELEASE_NOTES_1.1.13.md; Steam text: DOCS/release-notes/steam/STEAM_RELEASE_NOTES_1.1.13.txt.

Changes

  • Main Street: laid out in districts and hubs (Merchant Row, Guild Row, Home & Hearth, Castle Grounds, Status, Notice Board, and Online in online mode), driven by one place table (MainStreetDistricts.cs). Each screen shows only its unlocked keys; the legacy hidden keys and 88 dead Loc keys per language are removed; the level tiers are unchanged; unlock announcements name the district; Main Street's [?] opens a place map. The previous layout is kept at tag mainstreet-classic (4c50c11).
  • Playtest fixes:
    • XP lines that add up;
    • kept typeahead at online pauses;
    • an auto-combat heal threshold (20-70%, five save sites);
    • floor 4-6 softening from a table;
    • monster holds on the player through the PvP control rules;
    • a combat status line;
    • /train and a level-10 notice;
    • re-prompting menus and room events;
    • the chest's Search for Traps;
    • the Old God tutorial rule;
    • role-aware Equip Best.
  • NPCs: no Roman numerals in new names (a colliding child gets another first name with the family surname); memory timestamps kept, with a one-time grace; grudge and marriage clean-up on delete.
  • Shared records:
    • production (one game process): immediate versioned persist of the delete clean-up under a roster lock; a web admin delete through admin_commands with a claim;
    • self-hosted multi-process setups: a world_edits log re-applied by the owner process; door roster saves under the loaded version, with overlay, tombstones and retry.
  • Royal court: every treasury and court change goes through one versioned read-modify-write (OnlineStateManager.ApplyCourtChangeAsync, CrownAsync), with cost and effect in one write and the player's side after it lands. A comment-stripped source check fails on any court member change, or whole-king replacement, outside it.
  • Fixes:
    • the guard-bonus overflow;
    • sales tax credited twice;
    • zero tax reset to defaults;
    • courtier namesakes;
    • bail;
    • pardons without a record;
    • orphan re-graduation;
    • failed challenges;
    • coronations undone by court politics;
    • Equip Best's same-name removal and the equip menus' save order;
    • the delete purge's sender mail;
    • throne challenges against the real king.

Deploy note

The first start creates the world_edits, pending_purges and mud_heartbeat tables and adds the pending_purges columns; no manual database step. Self-hosted setups with one process per player must run player processes with --no-worldsim beside one --worldsim process.

Listed for 1.1.14

  • Royal petitions can overspend the treasury (NPCPetitionSystem ~1154).
  • A dead monarch's vacancy is not persisted (CastleLocation ~8581).
  • Street-imprisoned NPC sentences never expire online (StreetEncounterSystem ~3041).
  • A huge donation overflows Crown standing on the addition (FactionSystem ~360).
  • Display: the status sheet's doubled separators, Danger: : on the dungeon screen, room text wrapping mid-word.

Tests

1,995 passing, up from 1,731.

binary-knight and others added 30 commits September 24, 2026 16:11
A taken name now gets a surname from the immigrant and family pools, or a
different surname keeping the first name; children keep the family surname
with a middle surname. Trailing numerals are stripped from candidates, and
the guid fragment fallback is gone. Existing names are not renamed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- Equip Best and every manual equip menu (Team Corner, Home, Inn,
  Dungeon) take the exact backpack item chosen, not the first of that
  name, so two items with one name can no longer swap stats.
- The Home, Inn and Dungeon equipment menus save each gear move at once,
  in the order that cannot copy an item (shared-state NPCs); companions
  live in the player's save. Dungeon unequip of a cursed item no longer
  hands the player a copy.
- The delete purge keeps mail sent by a living character who goes by
  the deleted account's name.
- The king's save is found by display name, then Name2, then username,
  so a throne challenge and the king-level reads meet the real king
  rather than a stand-in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…at once

- The purge clears grudges against the name (memories recorded at or before
  the delete), and removes it from Enemies, KnownCharacters and negative
  impressions while no other player row uses the name.
- An NPC married to the deleted player is divorced, with its registry entry.
  An NPC married to another NPC of that name keeps its marriage.
- OnlineStateManager.PersistNpcWorldNow writes the roster right after the
  clean-up, with nothing awaited in between, under the record's version. On a
  conflict it reloads the stored roster, applies the clean-up again and
  retries. The stored marriages record loses the ended marriages the same way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- SaveRoyalCourtToWorldState writes through SaveRoyalCourtIfVersionAsync,
  against the stored version the in-memory court was loaded from or last
  written as. On a conflict it loads the stored court and skips the write.
- The version is process-wide like the king: session loads, the world sim's
  load and save, and every versioned court write record it.
- A court never loaded may write only where none is stored yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- The web delete queues an admin_commands 'delete_player' row and waits up to
  20 s for its status. The game server reads the Name2, runs DeleteGameData,
  then runs the purge only if the delete succeeded, as the admin consoles do.
- The admin poller writes mud_heartbeat. With no fresh heartbeat, or no answer
  in time (the command is then withdrawn), the web server deletes the rows
  itself and queues the purge in pending_purges. The game server runs queued
  purges once its world sim has loaded the roster.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…/train, choices

- The world event lines say they are included in the XP gained, and one
  line names the other XP modifiers; the share line matches the gain.
- A line typed at a pause online is kept and run next; the online pause
  says "Press Enter".
- Auto-combat drinks a potion at or below a chosen HP share (20-70%,
  default 50%), set in the preferences.
- Monsters on floors 4-6 are softened (0.85/0.9/0.95, a table), and a
  monster's stun or web on the player follows the PvP hold rules, so it
  cannot chain.
- The combat status line shows turns left, damage per turn, turns lost
  to a hold, and whether Last Stand and Death's Door are ready.
- /train takes you to the Level Master from Main Street or says how to
  get there; level 10 is announced.
- Menus and room events ask again on an invalid choice instead of
  leaving; a room event is spent by the player's choice; the chest's
  Search for Traps works; room actions say why they cannot be used; the
  translated prompts show the keys the code accepts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…ed throne save, web admin delete)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- A restored memory keeps its saved time; restoring used to stamp every
  memory with the load time, so memories never aged and the 7-day decay
  never fired.
- A roster saved before this version (no MemoryTimesKept mark) gets a
  one-time grace on its first load: a memory older than 7 days, or with no
  time, is stamped with the load time, so nothing is forgotten on the day of
  the update. Both serializers write the mark.
- The delete's grudge clean-up uses the delete time as its cut-off on
  retries too, since a reload now keeps the recorded times.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- ExecuteAdminCommand moves the row from pending to executing first and runs
  it only if that claim changed exactly one row. A command the web server
  withdrew, or one an overlapping poll already claimed, is not run.
- The web delete keeps waiting while its command is pending or executing.
  Its withdrawal still changes only a pending row; when the game server has
  claimed it, the web server waits up to 2 more minutes for the outcome and
  answers 504 if it is still running. It never deletes the rows itself after
  a claim.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- world_edits(id, kind, payload, created_at, created_by, applied_at,
  applied_by), made in the init script and again in the migrations for a
  database from an older release.
- AppendWorldEdit; GetWorldEditsToApply (every unapplied edit, plus the
  last 24 h); MarkWorldEditsApplied (the first mark stands);
  PruneAppliedWorldEdits (applied edits after 7 days, never unapplied ones);
  GetUnappliedWorldEditsOlderThan for the warning line.
- LaterCharacterUsesName: a player row with a save under the name created or
  saved after the edit, or on the deleted character's own account.
- WorldSimLockOwner reads the world sim lock's owner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- A delete appends a forget_character edit {aliases, character_key,
  deleted_at, untimed} before its local clean-up and versioned write.
- The owner is the MUD process, else the process whose world sim holds the
  worldsim lock. The MUD and the standalone --worldsim now take the lock and
  pass their id as heartbeatOwnerId, so door sessions start no second world
  sim.
- The owner applies every unapplied edit and every edit of the last 24 h
  after it loads the world, and in each save after any version-triggered
  reload and before the roster write. It marks an edit applied only after its
  versioned write succeeds; while one is owed the write is not skipped by the
  dirty-check. A process that is not the owner never marks an edit.
- Timed memories go by the delete time. Enemies, KnownCharacters,
  impressions and spouses are skipped once a later character uses the name
  (a player row with a save created or saved after the edit, or the same
  account).
- The owner prunes applied edits after 7 days and logs a warning for any
  edit unapplied after 24 h; those are never pruned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- SaveSharedNPCs in a process that is not the owner no longer writes the
  npcs record unconditionally. LoadSharedNPCs records the version it read,
  and the online loads note a hash of each NPC as restored.
- The save applies the world edits (never marking them) and writes under
  that version. On a conflict it loads the stored roster, lays over it each
  NPC this session changed since its load (an NPC the stored roster no longer
  has is not brought back), applies the edits again and retries under the
  new version. Other processes' changes to the other NPCs are kept; on an NPC
  both changed, the door's copy wins, as every door write did before. Each
  conflict is logged; after 5 it gives up with a warning and keeps the
  reloaded roster.
- The emotional state drifts on every read, so it is left out of the hash.
- The MUD process, and the door whose world sim holds the lock, are the
  owner and keep the unconditional write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- Deleting a character who reigns (in this process or in the stored court)
  appends a vacate_throne edit {king, aliases} before the abdication's
  versioned write; the owner marks it once that write lands.
- The owner re-applies it with the other edits: the reign ends only if the
  current king is still that character and no later character uses the
  name, with the usual NPC succession, unpersisted.
- The world sim's court save now reports whether the stored court is its
  own. A vacancy a throne edit made is written as one (the save used to skip
  a court with no king), and throne edits are marked only after that
  versioned write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…name

The maintainer's choice: a colliding child keeps the family surname and
takes another first name from its own sex's pool ("Talia Holloway").
Only when every first name in that pool is taken with the surname does
it fall to two first names before the surname ("Wren Talia Holloway");
the surname stays last and the child is not renamed on load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…y times)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
- A treasury withdrawal, deposit, loan or donation changes the player's
  gold only after the court write lands under the version it read; other
  treasury writers' unsaved changes are carried across a reload.
- The immediate purge writes under the version the live roster was loaded
  at, and on a conflict reloads, overlays, re-applies and retries.
- A purge and a roster rebuild share one lock, and a partial roster is
  never written; the edit log carries the clean-up instead.
- The forget edit ends registry marriages by the character's ID, whatever
  an NPC's spouse field says.
- A door save decides its own NPC changes before applying edits, keeps
  NPCs it created, and never revives ones another writer removed.
- Queued purges keep the delete time, the untimed decision, the character
  ID and the married display name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
One helper reads the stored court and its version, applies a single
change to that copy, writes it under the version and retries on a
conflict; the in-memory court is then the written copy. A change's cost
and benefit go in the same write, and the player's side (gold, guards,
pardons, bail) follows only once it lands. Treasury moves, audiences,
guards, prison records, decrees, orphans, marriage, heirs, petitions,
sales tax, daily activities and the world sim's tax, upkeep and court
politics all use it. The carried unsaved treasury delta is gone; the
periodic court save reloads on a conflict and never overwrites.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
When a delete's purge runs later (the game server was down), a players
row on the key or name with a save created or used at or after the
delete means the name belongs to a new character: its guild, auctions,
mail, quests, succession, throne and children are left alone, and only
the timed NPC memory clean-up runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Every reload records removed NPCs, so a save that conflicts again and
again never brings one back, while NPCs created in this session are
still kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
A bonus of 0 or less, above 1,000,000, or above the treasury split over
the guards is refused before it is multiplied; the multiply is checked
and the loyalty gain capped. A huge bonus used to overflow to a negative
cost that added gold to the treasury. Donate's crown-standing gain is
bounded too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Winning the throne by an NPC challenge, the sim claiming an empty throne,
and a player's challenge or siege write the new court in one versioned
write before anything else reads it; the flags and news follow only a
landed write. The deposed king's and a failed challenger's cells are
court changes too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The guards' loyalty rise is applied in the same court change as the
budget it spends, so a later court change or reload keeps it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Each sale credits the crown's share at once; the daily reset no longer
credits the day's takings a second time, and still reports them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
A graduating orphan is removed from the court in a versioned write
before its NPC is made, and new orphans are added the same way, so a
later court change can no longer bring a graduate back to graduate
again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The guards and monsters a challenger beat, the missed-defence loyalty
penalty and the desertions are written with the challenger's cell, so
the cell's write no longer restores them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…red at once

Each writes the new court (or a marked vacancy) in one versioned write;
the player's flags, marriage clean-up, gold and news follow only a
landed write, and a failed one tells the player and changes nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
binary-knight and others added 16 commits September 25, 2026 04:11
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The loaders apply the default rates only when a rate is missing from the
stored court, never to a stored 0, online and single-player.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
A courtier is matched by position, name, role and faction, never by name
alone, so namesakes are no longer both dismissed or the wrong one
promoted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The bail is set on the stored prison record in a court change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
A pardon removes the court record when there is one; an ordinary arrest
without one is released and nothing is written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The advisor appointment, the guard loyalty penalty and a dethroning in
the street throne challenge are written in court changes. The encounter
has no caller today.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The served-sentence release and the single-player guard upkeep use court
changes; unused King mutators are gone; a comment-stripped check over
every court member and entry field, with no allowlist, finds none left.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…t once

Ending a reign by ascending at the Pantheon or through an ending writes
the successor's court (or a marked vacancy) in one versioned write, and
the player's flags, marriage clean-up and news follow only a landed
write. The castle's fallback court for a player king is written only
when no court is stored; otherwise the stored court is loaded. The
source check now also flags a whole-king replacement outside the named
loaders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
The first visit after a menu tier rises names what just opened; an
existing character is not told about places it already had.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
You may retreat or leave; you cannot go deeper until that god is
defeated or won over.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
A tank favours defence, health and strength, a caster wisdom,
intelligence and mana, a healer wisdom and mana, a damage dealer its
attack stats; an item with nothing useful never beats one that has.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Main Street shows its places, then Merchant Row, Guild Row, Home &
Hearth, Castle Grounds, Status, the Notice Board and, online only, the
Online hub, each opening a short screen of its places. Every screen
shows only its unlocked keys and every key does what it shows; the old
hidden keys are gone and R returns from each screen. The unlock levels
are unchanged, a district appears once anything in it opens, and the
unlock notice names the district. News is one entry, [?] opens a map of
Main Street, and an existing character is told once that the street was
reorganised. The previous layout is kept at tag mainstreet-classic.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Every text that pointed at an old Main Street key now names the
district and key, for example Guild Row [G], Level Master [V].

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
…atus sheet

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CT1VB81uLs5VNysGendLKY
@binary-knight
binary-knight merged commit 19a76c6 into main Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant