Commit graph

13 commits

Author SHA1 Message Date
Stephen Dennis
04541ed603 nls: do not translate decompile @set/@power command templates
decompile_flags and decompile_powers emit executable MUSH script for
@decompile replay, not player prose. Marking them M_() made LANGUAGE=xx
emit "[xx] @set Foo=WIZARD", which does not replay. Revert to T(); drop
the msgids from the catalogues.

Also reverts @power from #1671 — same decompile-script class. Flagged
on #1674 review.
2026-07-28 13:57:48 +00:00
Stephen Dennis
8c466171c5 nls: mark object/flags/boolexp create-and-flag notify prose with M_()
Phase 3 coverage slice. Player and staff notifies that still used T()
(cast-only) now use M_() so they extract into the catalogue:

  object  name taken/silly, deposit refund, @chown summary, parent/zone/
          home/dropto clears, floating room
  flags   Flags: header, type/flag parse errors, @set decompile, flag
          name removed
  boolexp match “don’t see / which”
  wiz     password changed by %s
  player  @protect all listing line

Left alone: log messages, HTML, softcode/machine tokens, concatenated
fragments (set @chown owner line, rob give pieces), and punctuation
separators. pot 704 -> 722; xx filled; ko msgmerge only.
2026-07-28 13:06:50 +00:00
Stephen Dennis
995e3853bf nls: mark player/flags notify prose with M_ (#1419)
Next notify slice while set/speech-look PRs are in review: player
lifecycle and flag chatter (~37 sites). Leave tprintf formats as T().
Half-mark pass clean. Regenerate pot/xx without fuzzy entries.
2026-07-27 11:00:47 +00:00
Stephen Dennis
b5ee3b6f81 refactor: use UTF-8 text in user-facing string literals (#1513)
Replace ~900 typographic \xE2\x80\x.. escapes (curly quotes, en dashes)
and a few \xE2\x80\230 octal workarounds with real UTF-8 in message
strings under mux/modules and mux/src. Leave stringutil and convert
charset mapping tables as explicit byte sequences.
2026-07-26 21:52:48 -06:00
Stephen Dennis
e7eb6ec76d nls: route literal #-1 softcode tokens through S_ (#1475)
Mechanical hygiene under the opt-in M_() design: replace T("#-1…") and
T("#-2…") with S_() so softcode ABI tokens are obvious in source and
cannot enter a player catalog. ~400 call sites across engine, exp3,
mail, and driver. Assembled/library-spliced diagnostics (plan §4.2)
are unchanged where they are not a single literal.
2026-07-27 01:00:55 +00:00
Stephen Dennis
4579ed05ce fix: resolve Pass 8 user-facing defects #1182, #1184, #1186, #1187
Four unclaimed Pass 8 findings in four separate files, chosen to avoid the
files the open PR queue is already touching.  Each premise was re-verified
against current source before fixing.

#1182 p6h_vaht_crypt OOB read (player.cpp).  The guard only required
szSetting to be at least the 13-byte "$P6H$$1:sha1:" prefix, but the
timestamp is copied from a constant offset 54 bytes in (prefix + 40 hex
digits + separator).  Any A_PASS carrying that prefix with a total length
of 13..53 passed the check and then ran safe_str off the end of the
attribute value.  Reachable because mux_crypt classifies anything starting
"$P6H$" (and not "XX") as CRYPT_P6H_VAHT, so a truncated or corrupt A_PASS
-- raw attribute write or a damaged import, not @password -- reaches it on
the login path.  Require the whole fixed layout including the separator,
and fail closed to szFail.

#1184 CONNECTED leak via decode_flags (flags.cpp).  has_flag() and
flag_description() both hide the 'c' letter on Hidden(target) &&
!See_Hidden(player).  decode_flags() required (WIZARD | DARK) together, so
every dark non-wizard -- royalty, staff, any mortal able to set itself DARK
-- still emitted 'c' to examiners.  Hidden(x) is exactly (Flags(x) & DARK),
and decode_flags takes a FLAGSET rather than a dbref, so the aligned test
is DARK on the caller's flagset (unparse_object passes the target's).

#1186 moniker injection in look_contents (look.cpp).  look_exits()
html_escape()s exit names inside xch_cmd="...", and the anchor text in
look_contents() was already escaped, but CONTENTS_LOCAL and CONTENTS_NESTED
inserted Moniker() raw into the attribute.  A moniker containing a double
quote closed the attribute early and let the rest become further
Pueblo/HTML markup for HTML-capable clients.  CONTENTS_REMOTE was already
safe (it emits #dbref).

#1187 page_check charged before validating (speech.cpp).  payfor() ran
first; the not-connected and both A_LPAGE lock failures then returned false
with no refund, so a page that was never delivered still cost page_cost --
once per recipient, since do_page() calls page_check() per target.
Reordered to validate first and charge last; payfor() deducts only on
success, so no refund path is needed.  The wizard "can't return your page"
warning is now held until after payment, so a sender who cannot afford the
page is not told about one that never happens.

Behaviour change worth noting: when a sender both lacks funds and the
target is offline, the message is now "Sorry, X is not connected." rather
than "You don't have enough coins." -- the actual reason rather than the
one that happened to be checked first.

Adds tests/scenario/page_cost.py (wired into run.sh) for #1187, the only
one of the four reachable without an HTML client or manufactured
connection state.  It needs a mortal sender, since payfor() exempts
wizards outright and a Wizard-only test would pass against any
implementation.

Test validated against the unfixed build: cases 2 and 4 fail there
(offline page charged 5; two offline recipients charged 10, showing the
per-recipient amplification) while case 3 passes in both -- so it pins the
charge, not merely the absence of one.

The other three are not smoke-reachable: #1182 needs a crafted A_PASS,
#1184 needs live CONNECTED state on a dark player, #1186 needs an HTML
client.  Their normal paths are covered -- every scenario driver logs in
through mux_crypt, and all four suites pass.

Verified: build clean, smoke 1404/1404 0 crashes, scenario 4/4 drivers
(wild_capture, site_threshold, jit_perms, telnet_negotiation) plus the new
page_cost 4/4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:35:29 -06:00
Stephen Dennis
fcae41511b Fix command-side verb correctness bugs (@clone, @ps, whisper, @flag, @mark)
Correctness sweep of the command-side verb handlers. Five confirmed bugs
plus a help-text correction, verified by dual-lens review and code reading
(the read-only smoke harness can't exercise these verbs directly):

 - @clone/cost on an exit bypassed the "must control current location"
   check (it lived only on the non-/cost path), letting a builder splice a
   cloned exit into a room they do not control. (#855)
 - The @mark/@mark_all/@apply_marked DB-cleaning refusal cited @unmark_all,
   which does not exist (produces "Huh?"); corrected to @mark_all/clear.
   (#856)
 - @ps <object> reported nothing for a controlled object owned by another
   player; do_ps was missing the non-player-target else clause that the
   sibling @halt has (clear the owner filter). (#857)
 - whisper "<quoted name>" skipped the locality/connected gate the unquoted
   form applies, giving a success confirmation plus a delivery error and
   polluting A_LASTWHISPER; also fixed an adjacent quoted-name continue that
   did not advance the parser. (#858)
 - @flag/remove of an unknown/empty flag name was silent; now reports an
   error like the other flag-name failure paths. (#859)
 - report help said 8-hour segments but the code uses 4 (deliberately, per
   4a845139f); corrected the help. (#860)

Also restores the "## JIT / DBT Engine" CHANGES heading dropped during an
earlier 2.14.0.8 edit. Build clean, all 1264 smoke tests still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 06:29:11 -06:00
Stephen Dennis
1a770c2902 engine: add LBufPtr owning handle; convert a verified subset to RAII (#717)
Incremental progress on the alloc_lbuf/free_lbuf -> LBuf RAII migration, adding
the structural enabler the issue calls for and converting a small, individually
verified set of the harder (ownership-escaping) sites.

alloc.h:
  - LBuf gains release() (relinquish ownership to a caller) and reset() (free
    now, become empty). release() lets a producer that returns an lbuf use RAII
    for its early/error paths and hand the buffer off at the success return.
  - New LBufPtr: an owning, nullable, movable handle. Unlike LBuf (which always
    allocates and suits scoped scratch), LBufPtr can be empty, reseated, and
    released — for conditional ownership, storing an lbuf in a struct/queue
    entry, or producing a buffer to return. LBufPtr_Src / LBufPtr_Adopt mirror
    the LBuf macros.

Converted sites (each verified to neither double-free nor leak on any path):
  - flags.cpp: unparse_object_numonly, unparse_object — single-exit producers,
    now LBuf + release().
  - comsys.cpp: call_mogrifier — LBufPtr + release() (its early nullptr returns
    precede the allocation, so they are unaffected).
  - functions.cpp: switch_handler / switchall_handler — the ping-pong mbuff/tbuff
    scratch buffers now use LBuf RAII, removing six manual free_lbuf() calls
    including the error-prone early-return path in switch_handler. mbuff is only
    transiently aliased into mudstate.switch_token (restored before scope exit),
    so RAII scope-exit freeing preserves its lifetime.

The remaining ~145 manual sites (fargs[] stores, did_it() charge/runout swaps,
cross-function/struct lifetimes) still need per-site structural work; #717 stays
open. Build clean, all 1053 smoke tests pass (the corpus exercises switch()/
case()/unparse_object heavily).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:52:07 -06:00
Stephen Dennis
87485bc401 Migrate 22 alloc_lbuf/free_lbuf sites to LBuf RAII
Replace manual alloc_lbuf/free_lbuf pairs with LBuf RAII wrappers
in 12 engine source files: rob, walk, quota, wiz, session, object,
log, match, move, create, flags, boolexp.  This eliminates ~40
explicit free_lbuf calls on error paths that are now handled by
destructors, removing leak risk in early-return and multi-exit
functions (boolexp alone had 13 exit-path frees across two
functions).  Buffers returned by atr_get/atr_pget or to callers
are left manual since LBuf cannot adopt externally-allocated
buffers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:46:22 -06:00
Stephen Dennis
974964719c Convert all static scratch buffers to thread_local
43 static scratch-buffer arrays across 21 files (5 in mux/src, 38 in
mux/modules/) changed from `static` to `thread_local`. Under the
current single-threaded evaluator this is a zero-behavior-change swap
— `thread_local` storage has the same lifetime and zero-allocation
properties as `static` — but each thread gets its own copy, which
makes these functions safe for a future multi-threaded evaluator
without any locking.

Read-only constant tables (`aRadix64`, `aRadixPenn36`,
`aRadixPenn64`, `Empty`) left as `static` because they are immutable
shared data.

Affected areas:
  net.cpp        — queue_string co_buf, trimmed_site, dump_users NameField
  signals.cpp    — signal_desc
  stubslave.cpp  — Stub_PipePump
  attrcache.cpp  — sqlite_attr_buf
  boolexp.cpp    — parsestore
  command.cpp    — preserve_cmd, SpaceCompressCommand, LowerCaseCommand
  comsys.cpp     — NewTitle, Buffer, temp
  db.cpp         — tbuff, Buffer (x2)
  flags.cpp      — buff
  funceval.cpp   — textbuff
  functions.cpp  — TimeBuffer64, TimeBuffer80, Buffer
  help.cpp       — Line, Buffer
  mail.cpp       — aFolders, Buffer, res, szFittedMailAliasDesc
  match.cpp      — buffer
  player.cpp     — szSalt, buf (x2), buff
  plusemail.cpp   — buf
  predicates.cpp — Buf (x2), pName
  session.cpp    — szFittedDoing
  set.cpp        — pRestrictedKeyText
  unparse.cpp    — buf, boolexp_buf
  mail_mod.cpp   — result, res, buf

All 21 files verified with g++ -std=c++17 -fsyntax-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:21:14 -06:00
Stephen Dennis
a348e27f33 Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
Stephen Dennis
aaa5ac63ed Add TRUECOLOR player flag and wire Stage 6 rendering pipeline
New TRUECOLOR flag (FLAG_WORD2, 0x00400000) enables 24-bit color
output via ESC[38;2;R;G;Bm / ESC[48;2;R;G;Bm.  Players set it
with @set me=TRUECOLOR.

Rewire queue_string() in net.cpp to use the co_render_* pipeline:

  ANSI + TRUECOLOR → co_render_truecolor()
  ANSI + COLOR256  → co_render_ansi256()
  ANSI only        → co_render_ansi16()
  No ANSI          → strip_color() (unchanged)
  HTML             → convert_to_html() (unchanged)

The old convert_color() C++ function is no longer called from
the output path.  Charset conversion (Latin1/CP437/ASCII) still
runs as a second pass after color rendering.

Flag registered in flags.cpp with no flag letter (like COLOR256).
Help entry added with related topics cross-references.

505/505 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:59:44 -06:00
Stephen Dennis
4ff1398de1 Restructure mux/ directory: component-based layout with proper build root
Move from flat mux/src/ layout to clean component hierarchy:
- mux/ is now the autoconf/automake build root (configure.ac lives here)
- mux/include/ — shared headers used by multiple components
- mux/lib/ — libmux.so (core utilities, no game state)
- mux/src/ — netmux driver only (thin networking shell)
- mux/modules/engine/ — engine.so (game logic)
- mux/modules/{comsys,mail,exp3,sqlproxy,sqlslave}/ — external modules
- mux/ganl/ — GANL networking library
- mux/sqlite/ — SQLite amalgamation (builds libsqlite3.a)
- mux/announce/ — announce tool (was mux/src/tools/)

Build changes:
- SUBDIRS ordering: ganl sqlite lib src modules announce
- libmux.so gets -Wl,-soname,libmux.so; netmux links via -L -lmux
- engine.so links libsqlite3.a and libmux.so with -Wl,--no-undefined
- RPATH uses $ORIGIN for portable .so resolution
- Install hooks use absolute paths for game/bin symlinks

Bug fixes:
- engine.so mux_Register() now passes nullptr to mux_RegisterClassObjects
  (matches all other modules; libmux already has the factory via dlsym)
- DbConvert() now calls pcache_init() before db_write, fixing a latent
  crash (free(): invalid pointer) when exporting from SQLite databases

411/411 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:38:37 -06:00
Renamed from mux/src/flags.cpp (Browse further)