Commit graph

13 commits

Author SHA1 Message Date
Stephen Dennis
2285fa54be chore(int64): quota, charges, queue counts stay full width after mux_atoi64 (#1402)
Maintainer direction: prefer int64_t over long/int for parsed softcode
and attribute integers so LLP64 and LP64 behave the same.

This first slice covers the leftovers called out on the issue:

- quota: store/show/set paths (avoids @quota/all 2^32 writing 0)
- A_CHARGES in set/levels/predicates (RUNOUT vs wrap-to-positive)
- semaphore counts and @ps/queue kick/warp parsing
- IDLETIMEOUT attribute seconds

Bounded API edges (RunTasks(int), optional int *pnum) clamp after the
full-width parse. pot: quota notify formats use %lld.
2026-07-28 19:40:41 -06:00
Stephen Dennis
1862b248d7 nls: mark speech pose formats, look UI, engine/db set notifies (#1685)
Phase 3 coverage:

  speech   pose/semipose composition (%s %s / %s%s), shout glue
  look     exit destination and command-scan lines (not decompile)
  engine   @timecheck counted-objects summary (screen + log)
  db       @stats field sets, forward/permission errors

Decompile @create/@set templates, HTML, and softcode #dbref args stay T().
Regenerates pot; rebuilds xx.po for complete catalogue.
2026-07-28 15:16:04 +00:00
Stephen Dennis
59b3a2062e nls: whole-sentence page formats; no mixed T/M_ fragments (#1419)
Review on #1588: cases 2/3/default assembled the page echo from a T()
prefix, a conditional M_() recipient fragment, and a T() body — guaranteed
mixed-language under a translated catalogue.

Fold each branch into two whole-sentence msgids (recipient list present or
not). Document the rule in mux/po/README.md: mark all of an assembled
sentence or none of it.
2026-07-27 16:15:26 +00:00
Stephen Dennis
bfb163d652 nls: format-string slices for look, speech, and player (#1419)
Continue Phase 3 after create/set/wiz: mark player/staff tprintf and
safe_tprintf format templates with M_() so whole sentences (look examine
fields, say/page/whisper, login/alias/protect notices) enter the catalogue.

Left as T(): softcode decompile templates (@create/@dig/@lock…), ANSI color
attribute assemblies, pure name+message glue ("%s %s"/"%s%s"), machine
storage ("#%d", logindata, XX hash), and log_printf diagnostics.

Regenerate pot/xx/mo without empty msgstr. Format and NLS guards green.
2026-07-27 16:02:51 +00:00
Stephen Dennis
b78f1bd9e7 nls: mark speech/look notify prose with M_ (#1419)
Next notify slice: page/whisper/gag denials in speech.cpp and look/examine
chrome in look.cpp (~36 sites). Leave tprintf formats and HTML fragments
as T(). Half-mark pass promotes Contents: twin. Regenerate pot/xx.
2026-07-27 11:00:04 +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
2f106f200f fix(win32): migrate the remaining mux_atol callers to mux_atoi64 (#1373)
Completes the sweep the issue called for.  mux_atol returns long, which
is 32-bit on LLP64, so every caller silently truncated on Windows.  Two
of those were real defects (the truthiness family and cf_size, fixed in
the preceding commits); the rest were latent, waiting for a value large
enough to matter.

Rather than audit 290 sites for whether each can reach 2^31 today, use
the 64-bit parser everywhere and remove the class.  A dbref cannot
overflow now, but nothing stops a later caller passing that same site a
timestamp or a byte count.

Pure 1:1 substitution: 285 lines changed, and every removed line
contained mux_atol while every added line contains mux_atoi64.  No
control flow, no types, no behaviour beyond the wider parse.

This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so
the generated code there is unchanged.  It only widens the parse on
Windows.  Narrowing destinations are unaffected either way: `int x =
mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both
models.

Left alone: mux_atol itself in mathutil, its declaration, and three
comments that name it.  Callers that genuinely want 32-bit semantics can
still ask for them; none appear to.

Verified on Windows: full solution builds clean with no new warnings,
smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched --
identical to before the sweep, with the same 16 build-configuration
failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST).
Spot checks after the change: the boolean family returns 1 for multiples
of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited,
and arithmetic, string and list functions are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:12:35 -06: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
900e3e3175 Convert ~75 atr_get/atr_pget sites to LBuf_Adopt across 17 files
Use the new LBuf_Adopt() macro to take RAII ownership of caller-owned
pool buffers returned by atr_get, atr_pget, and atr_get_LEN.  This
eliminates ~82 explicit free_lbuf calls and automatically covers
early-return paths that previously required careful manual placement
of the free.

Heaviest conversions: player.cpp (13), comsys.cpp (14), command.cpp (9).
Complex interleaved patterns (did_it charge/runout swaps, PureName
reassignment, process_cmdent loops) left manual for now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:34:51 -06:00
Stephen Dennis
9b4c2946c2 Migrate ~55 alloc_lbuf/free_lbuf sites in large engine files to LBuf RAII
Fourth wave: ast (11), funceval (10), funceval2 (7), comsys (3),
speech (10), set (6), engine (7), command (10).  Covers the NOEVAL
handlers (cand/cor/if/switch/iter), function evaluators (ifelse,
letq, objeval, sortby, munge, while, sandbox), notify_check message
buffers, and command dispatch paths.  Buffers stored in fargs[]
arrays, returned through output pointers, or used in ping-pong swap
patterns are left manual.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:14:49 -06:00
Stephen Dennis
01138edbb2 Fix: whisper bugs — typo, space-in-names, silent empty whisper
1. Fix "to far" typo → "too far" in noisy_check_whisper_target().

2. Fix A_LASTWHISPER not saved for quoted names with spaces.
   The quoted-name parser NULs recipient[0] when the string starts
   with '"', so the guard "recipient[0] != '\0'" always failed.
   Replace with "nPlayers > 0" which correctly reflects whether
   targets were resolved.

3. Bare "w" with no arguments now reports "No one to whisper to."
   instead of silently returning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:31:07 -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/speech.cpp (Browse further)