Commit graph

35 commits

Author SHA1 Message Date
Stephen Dennis
015b620ec1 fix(websocket): drop empty frames; cap commands at LBUF-1 (#1818 #1819)
Empty TEXT/BINARY messages no longer call save_command (they bypassed
input_limit while still growing the input queue).  Payloads larger than
LBUF_SIZE-1 fail the connection with close 1009 instead of reaching
engine command buffers.

Harden space_compress to leave room for the trailing NUL when filling
SpaceCompressCommand (one-byte OOB at full LBUF of non-space).

Unit tests: empty flood, LBUF and LBUF-1 TEXT boundaries.
2026-07-31 06:26:08 -06:00
Stephen Dennis
b308dd3b8c feat(lua/jit): default lua_jit on (#1325)
The gates in #1325 are met and the closing of #1433 was the last one:

- The differential harness executes every supported shape -- 140
  chunks, EXEC requiring lua_run_ok to advance, declines all deliberate
  (os.time, two pins, select arity).
- All three loop forms run under an aborting instruction budget whose
  exhaustion surfaces the interpreter's own error, verbatim (#1732).
- The compiled path wins or ties every benched shape; loops beat the
  VM's marginal per-iteration cost after #1743 (#1741).
- #1433 closed unreproduced: three machines, thousands of process
  runs, including the original commit on the machine that first saw it.
- Windows gates closed by the maintainer.

A chunk the compiler cannot honestly run declines to the interpreter,
so OFF now buys nothing but the slower path; lua_jit=0 remains the
diagnostic switch.  With the default on, ordinary smoke exercises the
engaged path -- TC006 asserts lua_run_ok advances under the default
config -- and the explicit lua_jit-0 cases keep the gate itself tested.

smoke 1561/0 with the JIT engaged; luajit harness 140 chunks, 0 wrong;
full make test clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 21:16:55 -06:00
Stephen Dennis
bc4cec252e fix(nls): plural morphology baked into msgids, and a fragment that could not be translated (#1717)
Found while translating mail (#1419).  The tell was a line wrong in English
before translation is even involved:

    MAIL: 1 messages in folder 0 [unnamed] (1 unread, 0 cleared).

MN_() exists for this (#1622) and #1655 converted the @mail/stats family;
this set was missed.  A translator cannot work around it -- the msgid has no
msgid_plural, so there is no second form for a catalogue to supply, and es
must pick one wording for all counts while ru and pl need three and can
express none of them.  The fix is in the C++, not the .po.

## Converted, 11 sites

mail.cpp, mail_mod.cpp   mailbox is full; URGENT MAIL urgent messages
walkdb.cpp               universe contains; objects @chowned; objects marked
object.cpp               objects @chowned to you
wiz.cpp                  You toaded ... objects @chowned
command.cpp              at most N players logged in

The mail module duplicates are converted alongside the engine's so the two
implementations do not drift (#1614).

## One was a different bug than the issue described

walkdb.cpp built its message as M_("%d objects %smarked") with %s = "" or
"un".  That is fragment assembly, not a missing plural: a translator handed
that %s cannot use it, because the prefix is English morphology rather than
a word.  Split into two whole sentences, each pluralised -- the #1575 /
#1588 rule.  Now renders "1 object marked" / "8 objects unmarked" instead of
a %s a catalogue has no way to fill.

## Two deliberately NOT converted

    MAIL: %d messages in folder %d [%s] (%d unread, %d cleared).
    %d objects = %d rooms, %d exits, %d things, %d players. (%d garbage)

MN_(s, p, n) selects one form from one n.  Four independent counts are four
independent plural decisions, and in Russian each noun needs its own form.
These want rewording, which is a design call rather than a mechanical
conversion, so they are left and recorded rather than half-fixed.

## Catalogue fallout

.pot 18 -> 27 plural entries; all three catalogues msgmerge'd.

xx is `complete` policy, so its nine new entries had to be filled and
unfuzzied or the guard fails -- which it duly did, first try.  msgmerge had
copied the old singular-only text into BOTH forms, so msgstr[0] said
"messages"; each form is now prefixed from its own msgid.

es and ko received real translations for all nine.

    es.po  190 -> 198/976  (20.3%)
    ko.po  229 -> 236/976  (24.2%)

## Verified end to end, at n=1 and n>1

    en   1 object marked      8 objects marked
    es   1 objeto marcado     8 objetos marcados
    ko   사물 1개를 표시했습니다   사물 8개를 표시했습니다
    xx   [xx] 1 object marked [xx] 8 objects marked

Korean renders identically for both, which is correct: it declares
nplurals=1, so the catalogue's own rule is being honoured rather than
English's imposed on it.

The unconverted multi-count line still reads "1 messages", as documented.

make test: Smoke 1561 x3, tests/nls 7, ko 4, plural 43.  TESTEXIT=0.

Refs #1419, #1575, #1588, #1614, #1622, #1655, #1702.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 21:31:32 +00:00
Stephen Dennis
31088bde27 fix(tables): pot for C5–C9 labels; route attr hooks through hook_loop
Regenerate pot/xx/ko for new per-label msgids (Function Name, Hook Mask
Values, …).  Attribute @hook rows still used printf while the header and
command rows used mux_table — call hook_loop so both tables share C7.
2026-07-28 17:39:01 +00:00
Stephen Dennis
e1df4cab56 feat(tables): remaining staff lists via mux_table (#1667 Phase 4 C5-C9) 2026-07-28 17:38:11 +00:00
Stephen Dennis
5f43951f46 fix(tables): pot for C* labels; no %* width on mux_sprintf
Regenerate pot/xx/ko after Phase 4 C* (drop blob headers; add per-label
msgids).  Row right-just must use RightJustifyNumber — mux_vsnprintf does
not implement %* width (#1429), so the filed %*d/%*lld path would echo
literally and drop the rest of each stats line.
2026-07-28 17:34:14 +00:00
Stephen Dennis
5cee6cb721 feat(tables): staff list layouts via mux_table (#1667 Phase 4 C*) 2026-07-28 11:31:23 -06:00
Stephen Dennis
08ee03ba52 nls: mark cque/command/predicates/mail notify prose with M_() (#1681)
Phase 3 coverage, parallel to mux_table table-header work:

  cque         @ps timer labels, WaitQ warp, commands processed
  command      switch errors, dig/open/create/kill costs, money help,
               queue/login limits, lua/cache stats display
  predicates   not-enough-money, command table add/not-found
  mail         folder/send/clear/reject/stats *sentences* only

Left alone: mail %-column layouts, pose "%s %s", softcode __name keys,
NOPERM_MESSAGE, tabular predicate listings. pot 723-ish -> 795 msgids;
xx filled; ko msgmerge only.
2026-07-28 15:01:48 +00:00
Stephen Dennis
b9f18ee32e nls: replace no-placeholder tprintf(T()) notify constants with M_ (#1419)
A few sites wrapped constant prose in tprintf() with no format arguments
(comsys max-aliases message, Lua-module-loaded line, three mail admin
notices). Drop the no-op tprintf and mark with M_() so they enter the
catalog like other constant notify prose. Leave real format strings as
T(). Regenerate pot/xx without fuzzy entries.
2026-07-27 12:59:45 +00:00
Stephen Dennis
70683549b6
Merge pull request #1540 from brazilofmux/nls/notify-slice-command-list
nls: mark command list/icmd/hook notify prose with M_ (#1419)
2026-07-27 06:53:04 -06:00
Stephen Dennis
af79aa6cd0 nls: mark command list/icmd/hook notify prose with M_ (#1419)
Next notify slice: @list option blurbs, hash/module/reality chrome,
@icmd and @hook feedback in command.cpp (~52 constant sites). Leave
tprintf formats as T(). Half-mark pass clean. Regenerate pot/xx without
fuzzy entries.
2026-07-27 12:38:40 +00:00
Stephen Dennis
bfb57e27e1 fix(stubslave): say so when @list modules cannot reach the slave (#1535)
list_modules() enumerates a second time through the stubslave under
STUB_SLAVE, printing each entry as "<name> (loaded) by stubslave".  It
obtains the management interface with no else on failure, so the whole
slave section vanished silently and @list modules answered as though the
slave held nothing -- while the slave process demonstrably had the module
mapped.  The operator could not tell "nothing is loaded there" from "I
could not ask", which on the release build is the only question this
command exists to answer about the slave.

It fails every time today.  Measured on aarch64 with a throwaway game,
`module comsys_mod slave` in the config, and comsys_mod.so confirmed
present in /proc/<slave pid>/maps while the process ran:

    Modules:
    engine (loaded)
    libmux (unloaded)

No slave line, no error.  Instrumenting the path: list_modules creates a
second CID_StubSlave instance, Pipe_SendCallPacketAndWait succeeds, the
reply frame comes back with 0 of 8 bytes for cidProxy, and
mux_UnmarshalInterface reports MUX_E_CLASSNOTAVAILABLE (-3).  So the pipe
round trip happens and returns nothing to unmarshal.

That defect is NOT fixed here.  Repairing a repeat CreateInstance in the
marshalling layer is delicate work on a path with no test coverage that I
cannot validate on Windows, and it deserves its own change.  This makes it
visible instead of invisible -- the same fail-loudly argument as #1512,
where a silent fall-through turned an unreachable path into wrong data.

After:

    Modules:
    engine (loaded)
    libmux (unloaded)
    Could not query the stubslave module list (error -3).

Uses T() to match the surrounding raw_notify calls; whether operator
diagnostics should be M_() is an #1419 question, not one to settle inside
a one-branch fix.

Textually inside #ifdef STUB_SLAVE, so a default build is unaffected.
Verified on a --enable-stubslave build: make test smoke route 1 passes
1509/1509, 316/316 dispatched.

Note for anyone running the suite on this configuration: route 2 hit an
intermittent SEGV at shutdown, after all 316 files dispatched and all 1509
assertions passed, and did not recur in three further runs.  That is
#1433's character rather than anything this touches, and is reported
there; it is not caused by this change and is not fixed by it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 06:37:41 -06:00
Stephen Dennis
ffa0bfb84d nls: mark predicates/command notify prose with M_ (#1419)
Next notify slice while wiz/cque is in review: permission/lock-style
messages in predicates.cpp and command-processing chatter in
command.cpp (~45 sites). Leave tprintf formats as T(). Half-mark pass
clean. Regenerate pot/xx without fuzzy entries.
2026-07-27 11:49:19 +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
dc839ff1a3
Merge pull request #1307 from brazilofmux/fix/1280-noeval-boolean-hooks
fix(command): NOEVAL boolean hooks must read their content (#1280)
2026-07-25 23:14:32 -06:00
Stephen Dennis
8d6a7f29a4 fix(lua/jit): default-off lua_jit gate for safe loader land (#1309)
Phase 1 of the Lua JIT bring-up: mudconf.lua_jit (GOD/WIZARD) defaults
to false so the interpreter path remains the only production path while
hir_lower_lua correctness work continues.

TryJIT no-ops when the gate is off; @list lua reports enabled/disabled.
Documents the full phase plan in docs/plan-lua-jit-bringup.md.

Paired with the #1278 loader cherry-pick on this branch: master can take
both and stay green; developers flip lua_jit 1 to exercise the JIT.
2026-07-25 22:44:45 -06:00
Stephen Dennis
e7d6ccfff9 fix(command): NOEVAL boolean hooks must read their content (#1280)
process_hook() returns a bool: true means "proceed", false means a PERMIT
hook denies or an IGNORE hook swallows the command.  Under AF_NOEVAL -- or
a NOEVAL hook_obj -- it discarded the attribute's content outright and
returned true, so a NOEVAL permit hook allowed every command.  A
permission control silently disabled, and indistinguishable from having no
hook configured at all.

Call sites confirm the polarity: command.cpp:2163-2170 (and the four
copies at 2350, 2513, 2680, 2775) set hval on `!process_hook(...)`, so
true is "allow".  The default `retval = true` is correct for "no hook
attribute" -- it is only the NOEVAL branch that wrongly joins it.

Fixed by deriving the boolean from the unevaluated text.  That is the
semantic the rest of the NOEVAL work already uses rather than a preference
of mine: 4cb904fcc introduced AF_NOEVAL as "return raw text with no
function calls" across a list of sites, and eval_boolexp -- the only other
*boolean* consumer in that list -- compares the raw text
(`bCheck = !string_compare(buff, b->sub1)`) instead of short-circuiting.
process_hook was the outlier.  A literal "0" or "#-1 ..." hook now blocks
as written, and a literal "1" still permits.

Adds tests/scenario/hook_noeval.py.  Not in the smoke corpus: @admin
hook_obj / hook_cmd are CA_GOD for both read and write, and smoke test
cases run as the test object rather than #1.  The ufunction_fn.mux
@startup-on-#1 pattern does not carry over, because that publishes a
global any object can then call, whereas this needs the config pointed at
an object and the hook attribute mutated at run time.

The test validates against the unfixed build -- and the first draft did
not.  It used `@set obj/attr=NOEVAL`, but the attribute flag is NO_EVAL
(`noeval` is an unrelated command switch), so it set nothing, the hook was
evaluated normally, and all three cases passed against the *broken* build.
Corrected, and it now asserts the flag is really present (flags() must
report 'E') so it cannot silently degrade that way again:

  ok 1     - permit hook attribute is actually marked NO_EVAL
  not ok 2 - NOEVAL permit hook denying '0' blocks the command
             (lastcreate before='#17' after='#20')
  ok 3     - NOEVAL permit hook allowing '1' still permits

Case 3 passing in both builds is deliberate: it stops the fix from being
"deny all NOEVAL hooks", which would have been fail-closed but wrong.

Verified: build clean; smoke 1427/1427, 0 crashes, 305/305 dispatched;
scenario 7/7 drivers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 22:18:40 -06:00
Stephen Dennis
4d6fac656c fix: resolve 2.14 audit defects #1039–#1061
Restart (#1039–#1043): zero process-local DESC fields after load,
restore peer via getpeername, strip non-survivable TLS/WS flags,
drop TLS and WebSocket before exec, tear down adopt failures with
shutdownsock, abort partial restart loads cleanly.

DB (#1044–#1047): cache_flush_writes returns success only after
Commit; leave dirty queue on failure; atr_head union SQLite with
pending cache/queue; set mudstate.bStandAlone in DbConvert; flush
attr queue before import Commit.

Queue (#1048–#1052): setup_que refunds on OOM; sql_que frees BQUE
on Query fail and inits waittime; do_wait rolls back semaphore if
wait_que fails; include nest depth limit 50; CPU halt_que uses
Owner+object for machines.

JIT (#1053–#1057): full 64-bit rv_load_i64; setq/r single-char
RegisterSet; decline long CARGS; accumulate inline u() watermarks;
bounded guest_strnlen on ECALL paths.

Conf/convert (#1058–#1061): @enable/@disable sync g_dc; live
driver knobs for output/retry/max_players/timeouts/quota; safe
heap encode for t5x/t6h/r7h attributes.
2026-07-24 08:26:59 -06:00
Stephen Dennis
f068c5be21 engine: @dolist/now/break switch; ;| piping in inline command lists (#788)
Two follow-ups to the @dolist/now rewrite (37e18bef5):

- New /break switch: an @break inside the body still stops the loop,
  but with /break it also propagates to the enclosing command list,
  aborting the commands after the @dolist.  This restores the one
  ability the containment change removed (using a break inside the
  loop to abort the rest of a $-command), with sane loop-stop
  semantics, and mirrors @include (propagate by default, /nobreak to
  contain).  Without /now the switch is inert.

- ;| pipe segments now work in inline command lists.  The queued
  runner has always special-cased '|' after ';' to capture the
  previous command's output as %|; the inline loops in @dolist/now
  and @include treated such a segment as a literal command starting
  with '|' ("Huh?").  The splitting/pipe/break loop is factored into
  process_command_list_inline() in cque.cpp, shared by both callers
  and kept next to its queued twin so the semantics cannot drift.
  The inline list is its own pipe domain (save/restore of the
  enclosing pipe context), matching how each queued entry starts with
  a clean pipe state -- an inline list running inside a piped segment
  cannot corrupt the outer capture.

help @dolist documents /break and the ;| support.

Verified: default containment, /break propagation, and /break with no
inner break all behave as specified; pipes carry %| through inline
dolist and include bodies; smoke 1115/0/0 with three new test cases;
2x200 jit_diff clean.

Closes #788.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:00:21 -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
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
369f92495c Remove ISOUTOFMEMORY macro; add per-site OOM recovery and fix g_dump_child_pid
Eliminate the ISOUTOFMEMORY macro that unconditionally aborted on allocation
failure. Each of the 23 call sites now handles OOM appropriately:
- Fatal sites (buffer pools, db array, anum table): mux_assert or OutOfMemory
- Recoverable sites (queue, mail, commands, guests, vattrs, config, restart,
  forward lists): log the failure and return gracefully

Also fix g_dump_child_pid portability: volatile pid_t -> volatile sig_atomic_t
with explicit casts in ganl_adapter.cpp.

Close integer overflow issue as false alarm (getstring_noalloc uses a bounded
static buffer, so nBuffer+1 cannot wrap).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:31:20 -06:00
Stephen Dennis
03d017fc20 Implement Phase 4 routing: @walk and @patrol NPC primitives
Add server-side @walk <npc>=<destination> that moves an object one hop
per second along the routed path until arrival, and @patrol <npc>=<rooms>
for continuous waypoint loop movement. Both use the system scheduler
(DeferTask self-rescheduling, same pattern as @cron) and call move_exit()
for full movement side effects.

New route_next_exit() C-level API returns the next-hop exit dbref
directly, avoiding text format/parse overhead for the walk subsystem.

Switches: /stop cancels, /quiet suppresses notifications, /locked
validates exit locks during movement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 08:54:51 -06:00
Stephen Dennis
fa18bdae47 Replace stack-allocated LBUF arrays with pool-backed LBuf RAII wrapper
Add LBuf class to alloc.h: an RAII wrapper around alloc_lbuf/free_lbuf
that moves LBUF_SIZE buffers from the stack to the heap pool. Convert
all 108 non-static UTF8 xxx[LBUF_SIZE] stack arrays across 25 source
files. Static BSS buffers (24) are unchanged.

This eliminates LBUF_SIZE from recursive stack frames, making it safe
to increase LBUF_SIZE without risking stack overflow in the evaluation
pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:52:30 -06:00
Stephen Dennis
fe3cda5a79 Add PennMUSH and RhostMUSH feature adoption
PennMUSH: #lambda/#apply anonymous inline attributes, @include with
/nobreak /localize /clearregs, channel mogrifiers (MOGRIFY`BLOCK,
MESSAGE, FORMAT, OVERRIDE, NOBUFFER), cmogrifier().

RhostMUSH: between(), delextract(), garble(), caplist(), moon(),
soundex(), soundlike(), while(), crc32obj(), subnetmatch(),
wrapcolumns(), sandbox() with AST fallback for JIT compatibility.

21 new features/functions, 15 new smoke test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:08:21 -06:00
Stephen Dennis
d5239d2574 UFUN chain, ADDENT chain, qsort: STL conversion
Convert remaining medium-priority items to STL containers:

UFUN:
- ufun_head linked list (->next chain) → std::list<UFUN> ufun_list
- UTF8 *name (StringClone) → std::string name
- Insert/delete/iterate all simplified

ADDENT:
- ADDENT *next linked list → std::vector<ADDENT>* per CMDENT
- UTF8 *name (MEMALLOC/MEMFREE) → std::string name
- finish_cmdtab cleanup: chain walk+free → delete vector
- do_delcommand: pointer surgery → find+erase from vector

qsort:
- Last qsort() call in mail.cpp → std::sort() with typed comparator

All medium-priority STL conversion tracker items complete.
Net: −171 deleted, +105 added across 6 files. All 551 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:48:43 -06:00
Stephen Dennis
c0b6c5f93f Add @mail/unsafe switch 2026-03-18 12:56:16 -06:00
Stephen Dennis
e5b27ae93c Add in-memory LRU bytecode cache for Lua module
Compiled Lua chunks are cached as Lua registry references keyed by
source text.  LRU eviction when cache exceeds lua_cache_size (default
256 entries).  Second call to the same script skips compilation entirely.

Cache implementation:
- std::unordered_map<string, cache_entry> for O(1) lookup
- std::list<string> for LRU ordering
- luaL_ref/luaL_unref for Lua registry management
- CacheClear on shutdown/destructor

GetStats extended with cache_hits, cache_misses, cache_entries.
@list lua and @lua/stats now report cache statistics.

525/525 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 09:14:11 -06:00
Stephen Dennis
94f50afb65 Add @lua command, @list lua, and remaining bridge functions
@lua command:
- @lua obj/attr — execute a Lua script on an object
- @lua/inline code — execute inline Lua (wizard-only)
- @lua/stats — show Lua execution statistics

@list lua:
- Shows call count, errors, instruction/memory limit hits, bytes used

New mux.* bridge functions:
- mux.type(dbref) — object type string (ROOM/THING/EXIT/PLAYER)
- mux.flags(dbref) — flag string via DecodeFlags
- mux.isplayer(dbref) — boolean player check
- mux.isconnected(dbref) — boolean connected check
- mux.pennies(dbref) — object pennies
- mux.pemit(dbref, msg) — alias for mux.notify

525/525 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:38:55 -06:00
Stephen Dennis
0e619af49a Move pISlaveControl from mudstate to driver-side g_pISlaveControl
mudstate lives in engine.so (loaded via dlopen at runtime), so the
driver binary cannot reference it at link time — stricter linkers
reject the unresolved symbol.  Move the StubSlave lifetime pointer
to a driver-side global in driverstate.h.  Engine-side code that
needs mux_ISlaveControl now obtains its own proxy on demand through
mux_CreateInstance via libmux, matching the existing pattern used
for CID_QueryServer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 01:49:16 +00:00
Stephen Dennis
1c4bc725bb @protect: Add /alias, /unalias, and /all switches
New switches modeled after RhostMUSH's @protect:

@protect/alias <name>  — set a protected name as the player's alias.
  The name must already be in the player's protected list. Replaces
  any existing alias (TinyMUX supports one alias per player).

@protect/unalias <name> — remove the player's alias (must match).

@protect/all — Wizard-only. Lists all protected names across every
  player in the database.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 00:51:29 -06:00
Stephen Dennis
68e0c673e7 Feature: Add MSSP, GMCP, @protect, and benchmark()
Four new features identified from the TinyMUSH/PennMUSH/RhostMUSH survey:

MSSP (MUD Server Status Protocol, telnet option 70):
- Server sends structured key-value data (NAME, PLAYERS, UPTIME, PORT,
  CODEBASE, FAMILY) to MU* directory crawlers on IAC DO MSSP
- Stateless response via send_mssp() in telnet.cpp using g_dc config basket
- start_time_utc added to DRIVER_CONFIG for uptime calculation

GMCP (Generic MUD Communication Protocol, telnet option 201):
- Protocol negotiation: server offers WILL GMCP, tracks gmcp_enabled per DESC
- Inbound: GMCP subneg queued as synthetic "\x01GMCP" command, dispatched
  to handle_gmcp() which fires A_GMCP attribute with %0=package %1=json
- Outbound: gmcp(<player>, <package>, <json>) softcode function sends
  GMCP frames to all GMCP-enabled descriptors via SendGmcp COM method
- Full COM architecture: mux_IConnectionManager::SendGmcp in driver,
  send_gmcp() bridge in engine, CConnectionManager impl in modules.cpp

@protect (player name reservation):
- @protect[/add] <name>, @protect/del <name>, @protect/list [<player>]
- A_PROTECTNAME attribute (234) stores space-separated protected names
- protectname_check() hooked into create_player() and do_name()
- max_name_protect config param (default 5)

benchmark(<expression>, <iterations>):
- FN_NOEVAL, CA_PUBLIC, 10000 iteration cap
- Uses clock_gettime(CLOCK_MONOTONIC) / QueryPerformanceCounter
- Returns elapsed seconds as floating point

Also fixes: add unicode_tables.c to libmux.so LIBMUX_C_SRC (resolves
pre-existing tr_tolower_sbt etc. link errors).

505/505 smoke tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 22:08:55 -06:00
Stephen Dennis
48aca66fbf Add @cron/@crondel/@crontab with Vixie-style scheduler integration
5-field Unix cron syntax (minute hour dom month dow) with ranges,
lists, steps, and month/day-of-week names. Computed next-fire-time
scheduling integrates with CScheduler — no polling loop. In-memory
storage, recreated via @startup. DOM/DOW OR-semantics per Vixie cron.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 01:34:06 -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/command.cpp (Browse further)