2026-03-08 12:15:19 -06:00
|
|
|
|
/*! \file comsys_mod.cpp
|
|
|
|
|
|
* \brief Comsys Module — Channel system as a loadable module
|
|
|
|
|
|
*
|
|
|
|
|
|
* This module implements the MUX channel system as a dynamically loaded
|
|
|
|
|
|
* module. It hooks into server events for player connect/disconnect
|
|
|
|
|
|
* and provides mux_IComsysControl for command dispatch.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Core dependencies are accessed exclusively through COM interfaces:
|
|
|
|
|
|
* mux_INotify — player notification
|
|
|
|
|
|
* mux_IObjectInfo — object property queries
|
|
|
|
|
|
* mux_IAttributeAccess — attribute read/write
|
|
|
|
|
|
* mux_IEvaluator — softcode evaluation
|
|
|
|
|
|
* mux_IPermissions — permission checks
|
|
|
|
|
|
* mux_ILog — logging
|
2026-03-08 12:49:52 -06:00
|
|
|
|
*
|
|
|
|
|
|
* Channel data is loaded from and persisted to the game's SQLite database
|
|
|
|
|
|
* via the module's own sqlite3 connection.
|
2026-03-08 12:15:19 -06:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-09 20:38:37 -06:00
|
|
|
|
#include "copyright.h"
|
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
#include "libmux.h"
|
|
|
|
|
|
#include "modules.h"
|
2026-07-27 15:48:18 +00:00
|
|
|
|
#include "timeutil.h"
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Column layout: co_copy_field (#1649 / #1653 / #1656). cheader storage
|
|
|
|
|
|
// still uses StripTabsAndTruncate via core.h so ANSI collapse matches the
|
|
|
|
|
|
// engine's do_cheader. Plain assembly uses mux_sprintf (mux_format.h).
|
|
|
|
|
|
//
|
|
|
|
|
|
#include "core.h"
|
fix(comsys): six raw snprintf sites, and record the mail-size divergence
The ratchet added alongside #1631 caught #1640 landing 7 new raw snprintf
call sites in comsys_mod.cpp while that guard was being written -- the drift
it exists to stop, observed inside a single merge. Six format a player name
with a plain "%s" into a fixed buffer, which is exactly the case that emits
half a UTF-8 sequence, and are converted to mux_sprintf.
The remaining two format channel-list columns with "%-13.13s"-style
precision. mux_vsnprintf counts CODEPOINTS there where snprintf counts
bytes, so converting them silently re-widens every CJK column. That belongs
to #1649, which is about visual-width columns, so BAN_LEGACY records 78
rather than 77 with the reason written down.
Blessing the conformance baseline collapses the mail-stats divergence from
five lines to one character: with both implementations finally reporting the
same statistic, the engine's byte total is one lower than the module's for a
byte-identical body. Both store 26 characters and both accumulate
MessageFetchSize + 1, but the engine's accessor returns 25 -- inconsistent
with its own mailreview, which returns 26. Filed as #1652; not fixed here
because correcting it changes long-standing engine output.
Backlog for the remaining 242 legacy sites is #1653. The searchcost gap in
the module's stats is #1654.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 21:27:53 -06:00
|
|
|
|
#include "mux_format.h"
|
2026-07-28 19:43:03 -06:00
|
|
|
|
#include "mathutil.h"
|
2026-07-27 22:06:07 -06:00
|
|
|
|
#include "color_ops.h"
|
2026-07-28 08:02:10 -06:00
|
|
|
|
#include "mux_table.h"
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
#include "comsys_mod.h"
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
2026-03-27 17:20:20 -06:00
|
|
|
|
#include <atomic>
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
#include <cstdarg>
|
2026-03-08 12:49:52 -06:00
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
Rework Windows build for component-based directory layout
Adapt all vcxproj files and solution to the new directory structure
(src/ driver, lib/ shared library, modules/engine/ game logic,
modules/{comsys,mail,exp3,sqlproxy,sqlslave}/ loadable modules).
Key changes:
- libmux.dll exports utility symbols via LIBMUX_API macro
(__declspec(dllexport) when BUILDING_LIBMUX, dllimport otherwise)
- LIBMUX_API added to all shared headers: stringutil.h, timeutil.h,
mathutil.h, utf8tables.h, svdhash.h, svdrand.h, sha1.h, alloc.h,
dbutil.h, core.h
- Per-file PreprocessorDefinitions in libmux.vcxproj inherit from
project-level via %(PreprocessorDefinitions)
- Driver factory declarations (CDriverControlFactory,
CConnectionManagerFactory) guarded with BUILDING_DRIVER
- PCG-XSH-RR-64/32 (pcg32) for Windows (no __int128 needed);
Unix PCG-XSL-RR-128/64 unchanged
- MSVC portability fixes: _strnicmp, _BitScanForward64, (std::min)(),
HAVE_WORKING_FORK guards, WINDOWS_FILES/UNIX_FILES ModuleAdd paths
- Remove stubslave.cpp and slave.cpp from netmux.vcxproj (separate
processes)
- Fix sqlproxy/sqlslave vcxproj relative paths for new layout
- Add ws2_32.lib to engine.vcxproj for socket functions
- Add strcasecmp/strtok_r/strndup compat shims for comsys/mail
Builds successfully: libmux.dll, engine.dll, netmux.exe, exp3.dll,
sqlproxy.dll, sqlslave.dll. Comsys/mail blocked on sqlite3 linking
architecture (need COM-mediated or independent sqlite3 linkage).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:38:57 -06:00
|
|
|
|
// Windows compatibility for POSIX functions.
|
|
|
|
|
|
//
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
|
#define strcasecmp _stricmp
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-07-28 08:02:10 -06:00
|
|
|
|
// Column layout: libmux mux_table_* on co_copy_field (#1667 Phase 3).
|
|
|
|
|
|
//
|
|
|
|
|
|
#define append_ljust_field mux_table_append_ljust
|
|
|
|
|
|
#define append_bytes mux_table_append_bytes
|
2026-07-27 22:06:07 -06:00
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
// Module bookkeeping.
|
|
|
|
|
|
//
|
2026-03-27 17:20:20 -06:00
|
|
|
|
static std::atomic<uint32_t> g_cComponents{0};
|
|
|
|
|
|
static std::atomic<uint32_t> g_cServerLocks{0};
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
|
|
|
|
|
// Module entry points.
|
|
|
|
|
|
//
|
|
|
|
|
|
static MUX_CLASS_INFO comsys_classes[] =
|
|
|
|
|
|
{
|
|
|
|
|
|
{ CID_ComsysMod }
|
|
|
|
|
|
};
|
|
|
|
|
|
#define NUM_CLASSES (sizeof(comsys_classes)/sizeof(comsys_classes[0]))
|
|
|
|
|
|
|
2026-07-25 08:33:42 -06:00
|
|
|
|
// Required by libmux ModuleLoad — without this, bLoaded stays false and
|
|
|
|
|
|
// CreateInstance never finds the class (#1191 root cause).
|
|
|
|
|
|
//
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_CanUnloadNow(void)
|
2026-07-25 08:33:42 -06:00
|
|
|
|
{
|
|
|
|
|
|
if ( 0 == g_cComponents
|
|
|
|
|
|
&& 0 == g_cServerLocks)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_FALSE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_Register(void)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
MUX_RESULT mr = MUX_E_UNEXPECTED;
|
|
|
|
|
|
|
|
|
|
|
|
if ( 0 == g_cComponents
|
|
|
|
|
|
&& 0 == g_cServerLocks)
|
|
|
|
|
|
{
|
|
|
|
|
|
mr = mux_RegisterClassObjects(NUM_CLASSES, comsys_classes, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_Unregister(void)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
return mux_RevokeClassObjects(NUM_CLASSES, comsys_classes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_GetClassObject(MUX_CID cid, MUX_IID iid, void **ppv)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
MUX_RESULT mr = MUX_E_CLASSNOTAVAILABLE;
|
|
|
|
|
|
|
|
|
|
|
|
if (CID_ComsysMod == cid)
|
|
|
|
|
|
{
|
|
|
|
|
|
CComsysModFactory *pFactory = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pFactory = new CComsysModFactory;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pFactory)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mr = pFactory->QueryInterface(iid, ppv);
|
|
|
|
|
|
pFactory->Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CComsysMod — main module class.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CComsysMod::CComsysMod(void) : m_cRef(1),
|
|
|
|
|
|
m_pILog(nullptr),
|
|
|
|
|
|
m_pIServerEventsControl(nullptr),
|
|
|
|
|
|
m_pINotify(nullptr),
|
|
|
|
|
|
m_pIObjectInfo(nullptr),
|
|
|
|
|
|
m_pIAttributeAccess(nullptr),
|
|
|
|
|
|
m_pIEvaluator(nullptr),
|
2026-03-08 12:49:52 -06:00
|
|
|
|
m_pIPermissions(nullptr),
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
m_pIGameConfig(nullptr),
|
2026-07-25 08:33:42 -06:00
|
|
|
|
m_pIStorage(nullptr),
|
|
|
|
|
|
m_revision(0)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
g_cComponents++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::FinalConstruct(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
MUX_RESULT mr;
|
|
|
|
|
|
|
|
|
|
|
|
// Acquire logging interface.
|
|
|
|
|
|
//
|
|
|
|
|
|
mr = mux_CreateInstance(CID_Log, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_ILog,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pILog));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Register for server events.
|
|
|
|
|
|
//
|
|
|
|
|
|
mux_IServerEventsSink *pIServerEventsSink = nullptr;
|
|
|
|
|
|
mr = QueryInterface(IID_IServerEventsSink,
|
|
|
|
|
|
reinterpret_cast<void **>(&pIServerEventsSink));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
mr = mux_CreateInstance(CID_ServerEventsSource, nullptr,
|
|
|
|
|
|
UseSameProcess, IID_IServerEventsControl,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIServerEventsControl));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIServerEventsControl->Advise(pIServerEventsSink);
|
|
|
|
|
|
}
|
|
|
|
|
|
pIServerEventsSink->Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Acquire core interfaces.
|
|
|
|
|
|
//
|
2026-03-27 16:23:30 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_Notify, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_INotify,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pINotify));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
2026-03-27 16:23:30 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IObjectInfo,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIObjectInfo));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
2026-03-27 16:23:30 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_AttributeAccess, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IAttributeAccess,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIAttributeAccess));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
2026-03-27 16:23:30 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_Evaluator, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IEvaluator,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIEvaluator));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
2026-03-27 16:23:30 -06:00
|
|
|
|
mr = mux_CreateInstance(CID_Permissions, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IPermissions,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIPermissions));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
// Optional (#1654): an engine older than CID_GameConfig simply lacks the
|
|
|
|
|
|
// class, and every consumer defaults to the engine's compiled-in
|
|
|
|
|
|
// behaviour. Hard-failing here would make the module unloadable against
|
|
|
|
|
|
// a binary it otherwise works with.
|
|
|
|
|
|
//
|
|
|
|
|
|
mr = mux_CreateInstance(CID_GameConfig, nullptr, UseSameProcess,
|
|
|
|
|
|
IID_IGameConfig,
|
|
|
|
|
|
reinterpret_cast<void **>(&m_pIGameConfig));
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIGameConfig = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
// Log that we are alive.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
MUX_RESULT mr2 = m_pILog->start_log(&fStarted, LOG_ALWAYS,
|
|
|
|
|
|
T("INI"), T("INFO"));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr2) && fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module loaded."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CComsysMod::~CComsysMod()
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
// Release the storage interface.
|
2026-03-08 12:49:52 -06:00
|
|
|
|
//
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr != m_pIStorage)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIStorage->Release();
|
|
|
|
|
|
m_pIStorage = nullptr;
|
|
|
|
|
|
}
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
// STL containers clean up automatically.
|
2026-03-08 12:49:52 -06:00
|
|
|
|
//
|
|
|
|
|
|
m_channels.clear();
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_comsys.clear();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
MUX_RESULT mr = m_pILog->start_log(&fStarted, LOG_ALWAYS,
|
|
|
|
|
|
T("INI"), T("INFO"));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module unloading."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
m_pILog->Release();
|
|
|
|
|
|
m_pILog = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pIServerEventsControl)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIServerEventsControl->Release();
|
|
|
|
|
|
m_pIServerEventsControl = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Release();
|
|
|
|
|
|
m_pINotify = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->Release();
|
|
|
|
|
|
m_pIObjectInfo = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pIAttributeAccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIAttributeAccess->Release();
|
|
|
|
|
|
m_pIAttributeAccess = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pIEvaluator)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIEvaluator->Release();
|
|
|
|
|
|
m_pIEvaluator = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->Release();
|
|
|
|
|
|
m_pIPermissions = nullptr;
|
|
|
|
|
|
}
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
if (nullptr != m_pIGameConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIGameConfig->Release();
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
|
|
|
|
|
|
g_cComponents--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IComsysControl *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IComsysControl == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IComsysControl *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (IID_IServerEventsSink == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IServerEventsSink *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysMod::AddRef(void)
|
|
|
|
|
|
{
|
2026-04-05 15:03:38 -06:00
|
|
|
|
return m_cRef.fetch_add(1, std::memory_order_relaxed) + 1;
|
2026-03-08 12:15:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysMod::Release(void)
|
|
|
|
|
|
{
|
2026-04-05 15:03:38 -06:00
|
|
|
|
uint32_t prev = m_cRef.fetch_sub(1, std::memory_order_acq_rel);
|
|
|
|
|
|
if (1 == prev)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-04-05 15:03:38 -06:00
|
|
|
|
return prev - 1;
|
2026-03-08 12:15:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-10 10:39:15 -06:00
|
|
|
|
// Channel data loading via engine storage interface.
|
2026-03-08 12:49:52 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::LoadChannels(void)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT mr = m_pIStorage->LoadAllChannels(
|
|
|
|
|
|
[](void *ctx, const UTF8 *name, const UTF8 *header,
|
|
|
|
|
|
int type, int temp1, int temp2, int charge, int charge_who,
|
|
|
|
|
|
int amount_col, int num_messages, int chan_obj)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
CComsysMod *self = static_cast<CComsysMod *>(ctx);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto ch = std::make_unique<channel>();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr != name)
|
|
|
|
|
|
{
|
|
|
|
|
|
strncpy(reinterpret_cast<char *>(ch->name),
|
|
|
|
|
|
reinterpret_cast<const char *>(name), MAX_CHANNEL_LEN);
|
|
|
|
|
|
ch->name[MAX_CHANNEL_LEN] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr != header)
|
|
|
|
|
|
{
|
|
|
|
|
|
strncpy(reinterpret_cast<char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(header), MAX_HEADER_LEN);
|
|
|
|
|
|
ch->header[MAX_HEADER_LEN] = '\0';
|
|
|
|
|
|
}
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
ch->type = type;
|
|
|
|
|
|
ch->temp1 = temp1;
|
|
|
|
|
|
ch->temp2 = temp2;
|
|
|
|
|
|
ch->charge = charge;
|
|
|
|
|
|
ch->charge_who = charge_who;
|
|
|
|
|
|
ch->amount_col = amount_col;
|
|
|
|
|
|
ch->num_messages = num_messages;
|
|
|
|
|
|
ch->chan_obj = chan_obj;
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<UTF8> key(ch->name,
|
|
|
|
|
|
ch->name + strlen(reinterpret_cast<const char *>(ch->name)) + 1);
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
self->m_channels[key] = std::move(ch);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_SUCCEEDED(mr);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::LoadChannelUsers(void)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT mr = m_pIStorage->LoadAllChannelUsers(
|
|
|
|
|
|
[](void *ctx, const UTF8 *channel_name, int who,
|
|
|
|
|
|
bool is_on, bool comtitle_status, bool gag_join_leave,
|
|
|
|
|
|
const UTF8 *title)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
CComsysMod *self = static_cast<CComsysMod *>(ctx);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == channel_name) return;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
struct channel *ch = self->select_channel(channel_name);
|
|
|
|
|
|
if (nullptr == ch) return;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1195: skip invalid dbrefs; object listeners are allowed.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != self->m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
self->m_pIObjectInfo->IsValid(who, &bValid);
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser cu;
|
|
|
|
|
|
cu.who = who;
|
|
|
|
|
|
cu.bUserIsOn = is_on;
|
|
|
|
|
|
cu.ComTitleStatus = comtitle_status;
|
|
|
|
|
|
cu.bGagJoinLeave = gag_join_leave;
|
|
|
|
|
|
cu.bConnected = false;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr != title && title[0] != '\0')
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
cu.title = reinterpret_cast<const char *>(title);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
ch->users[who] = std::move(cu);
|
2026-03-10 10:39:15 -06:00
|
|
|
|
}, this);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return MUX_SUCCEEDED(mr);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::LoadPlayerChannels(void)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT mr = m_pIStorage->LoadAllPlayerChannels(
|
|
|
|
|
|
[](void *ctx, int who, const UTF8 *alias,
|
|
|
|
|
|
const UTF8 *channel_name)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
CComsysMod *self = static_cast<CComsysMod *>(ctx);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == alias || nullptr == channel_name) return;
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = self->m_comsys[who];
|
|
|
|
|
|
c.who = who;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
com_alias ca;
|
|
|
|
|
|
ca.alias = reinterpret_cast<const char *>(alias);
|
|
|
|
|
|
ca.channel = reinterpret_cast<const char *>(channel_name);
|
|
|
|
|
|
c.aliases.push_back(std::move(ca));
|
|
|
|
|
|
}, this);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
return MUX_SUCCEEDED(mr);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Internal data structure helpers.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *CComsysMod::select_channel(const UTF8 *name)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<UTF8> key(name,
|
|
|
|
|
|
name + strlen(reinterpret_cast<const char *>(name)) + 1);
|
|
|
|
|
|
auto it = m_channels.find(key);
|
|
|
|
|
|
if (it != m_channels.end())
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
return it->second.get();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Case-insensitive fallback.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : m_channels)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (0 == strcasecmp(reinterpret_cast<const char *>(name),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
reinterpret_cast<const char *>(kv.second->name)))
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
return kv.second.get();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *CComsysMod::select_user(struct channel *ch, dbref player)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (nullptr == ch)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = ch->users.find(player);
|
|
|
|
|
|
if (it != ch->users.end())
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
return &it->second;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &CComsysMod::get_comsys(dbref who)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = m_comsys[who];
|
|
|
|
|
|
c.who = who;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
return c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *CComsysMod::get_channel_from_alias(dbref player, const UTF8 *alias)
|
|
|
|
|
|
{
|
|
|
|
|
|
static const UTF8 empty[] = { '\0' };
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(player);
|
|
|
|
|
|
if (it == m_comsys.end())
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
const comsys_t &c = it->second;
|
|
|
|
|
|
for (const auto &ca : c.aliases)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (ca.alias == reinterpret_cast<const char *>(alias))
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
return reinterpret_cast<const UTF8 *>(ca.channel.c_str());
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Connect/disconnect helpers.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
void CComsysMod::do_comconnectraw_notify(dbref player, const UTF8 *chan)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(chan);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
struct comuser *cu = select_user(ch, player);
|
|
|
|
|
|
if (nullptr == cu)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Only send if channel is LOUD, user is on, and player is not hidden.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( (ch->type & CHANNEL_LOUD)
|
|
|
|
|
|
&& cu->bUserIsOn)
|
|
|
|
|
|
{
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
2026-03-08 12:49:52 -06:00
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetName(player, &pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
pName = T("???");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("%s %s has connected."),
|
2026-03-08 12:49:52 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(pName));
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : ch->users)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser &u = kv.second;
|
|
|
|
|
|
if (u.bConnected && u.bUserIsOn && !u.bGagJoinLeave)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pINotify->RawNotify(u.who, msg);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
void CComsysMod::do_comdisconnectraw_notify(dbref player, const UTF8 *chan)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(chan);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
struct comuser *cu = select_user(ch, player);
|
|
|
|
|
|
if (nullptr == cu)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( (ch->type & CHANNEL_LOUD)
|
|
|
|
|
|
&& cu->bUserIsOn)
|
|
|
|
|
|
{
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
2026-03-08 12:49:52 -06:00
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetName(player, &pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
pName = T("???");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("%s %s has disconnected."),
|
2026-03-08 12:49:52 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(pName));
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : ch->users)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser &u = kv.second;
|
|
|
|
|
|
if (u.bConnected && u.bUserIsOn && !u.bGagJoinLeave)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pINotify->RawNotify(u.who, msg);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
void CComsysMod::do_comconnectchannel(dbref player, const UTF8 *channel,
|
|
|
|
|
|
const std::string &alias)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(channel);
|
|
|
|
|
|
if (nullptr != ch)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
if (nullptr != user)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
user->bConnected = true;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
else if (nullptr != m_pINotify)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Bad Comsys Alias: %s for Channel: %s"),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
alias.c_str(),
|
|
|
|
|
|
reinterpret_cast<const char *>(channel));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Bad Comsys Alias: %s for Channel: %s"),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
alias.c_str(),
|
2026-03-08 12:49:52 -06:00
|
|
|
|
reinterpret_cast<const char *>(channel));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
void CComsysMod::do_comdisconnectchannel(dbref player, const UTF8 *channel)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(channel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
if (nullptr != user)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
user->bConnected = false;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-10 10:39:15 -06:00
|
|
|
|
// Write-through helpers — delegate to engine storage interface.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-07-25 08:33:42 -06:00
|
|
|
|
void CComsysMod::bump_revision(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Wrap-safe for softcode gen-sync; softcode treats any change as dirty.
|
|
|
|
|
|
//
|
|
|
|
|
|
m_revision++;
|
|
|
|
|
|
if (m_revision < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_revision = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
// Report a refused storage write (#1630). Silent on success, so it can wrap
|
|
|
|
|
|
// a call directly: log_storage_failure(m_pIStorage->Foo(k), "Foo(k=%d)", k);
|
|
|
|
|
|
//
|
|
|
|
|
|
// The message carries the operation, the key that identifies the row, and the
|
|
|
|
|
|
// result code. #1620's history-write log is the same idea spelled out at one
|
|
|
|
|
|
// call site; this is that shape made cheap enough to use at all of them.
|
|
|
|
|
|
//
|
|
|
|
|
|
void CComsysMod::log_storage_failure(MUX_RESULT mr, const char *fmt, ...)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( MUX_SUCCEEDED(mr)
|
|
|
|
|
|
|| nullptr == m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char detail[192];
|
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
va_start(ap, fmt);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_vsnprintf(reinterpret_cast<UTF8 *>(detail), sizeof(detail), reinterpret_cast<const UTF8 *>(fmt), ap);
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("DB"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 buf[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(buf, sizeof(buf),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("Comsys module: storage write refused: %s; result %d."),
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
detail, mr);
|
|
|
|
|
|
m_pILog->log_text(buf);
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
void CComsysMod::sqlite_wt_channel_user(const UTF8 *channel_name,
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
const comuser &user)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage) return;
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->SyncChannelUser(channel_name, user.who,
|
|
|
|
|
|
user.bUserIsOn, user.ComTitleStatus, user.bGagJoinLeave,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(user.title.c_str())),
|
|
|
|
|
|
"SyncChannelUser(channel=%s, who=#%d)",
|
|
|
|
|
|
reinterpret_cast<const char *>(channel_name),
|
|
|
|
|
|
static_cast<int>(user.who));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::sqlite_wt_channel(struct channel *ch)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage) return;
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->SyncChannel(ch->name, ch->header,
|
|
|
|
|
|
ch->type, ch->temp1, ch->temp2, ch->charge, ch->charge_who,
|
|
|
|
|
|
ch->amount_col, ch->num_messages, ch->chan_obj),
|
|
|
|
|
|
"SyncChannel(channel=%s)",
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
void CComsysMod::sqlite_wt_player_channel(dbref who, const UTF8 *alias,
|
|
|
|
|
|
const UTF8 *channel_name)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage) return;
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(
|
|
|
|
|
|
m_pIStorage->SyncPlayerChannel(who, alias, channel_name),
|
|
|
|
|
|
"SyncPlayerChannel(who=#%d, alias=%s, channel=%s)",
|
|
|
|
|
|
static_cast<int>(who), reinterpret_cast<const char *>(alias),
|
|
|
|
|
|
reinterpret_cast<const char *>(channel_name));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::sqlite_wt_delete_player_channel(dbref who, const UTF8 *alias)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage) return;
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->DeletePlayerChannel(who, alias),
|
|
|
|
|
|
"DeletePlayerChannel(who=#%d, alias=%s)",
|
|
|
|
|
|
static_cast<int>(who), reinterpret_cast<const char *>(alias));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::sqlite_wt_delete_channel_user(const UTF8 *channel_name,
|
|
|
|
|
|
dbref who)
|
|
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == m_pIStorage) return;
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->DeleteChannelUser(channel_name, who),
|
|
|
|
|
|
"DeleteChannelUser(channel=%s, who=#%d)",
|
|
|
|
|
|
reinterpret_cast<const char *>(channel_name), static_cast<int>(who));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Channel access checks.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-07-24 13:18:24 -06:00
|
|
|
|
// Attribute numbers for channel-object locks (attrs.h A_LOCK/A_LUSE/A_LENTER).
|
|
|
|
|
|
// Duplicated here so the module does not include engine attrs.h.
|
|
|
|
|
|
//
|
|
|
|
|
|
static constexpr int kA_LOCK = 42;
|
|
|
|
|
|
static constexpr int kA_LENTER = 59;
|
|
|
|
|
|
static constexpr int kA_LUSE = 62;
|
|
|
|
|
|
|
|
|
|
|
|
// #1084: type-bit OR could_doit on channel object — engine comsys parity.
|
|
|
|
|
|
//
|
|
|
|
|
|
static bool channel_lock_ok(mux_IPermissions *pPerm, dbref player,
|
|
|
|
|
|
dbref chan_obj, int atr)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pPerm || NOTHING == chan_obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
|
pPerm->CouldDoit(player, chan_obj, atr, &ok);
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
bool CComsysMod::test_transmit_access(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(player, &bCommAll);
|
|
|
|
|
|
if (bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int access;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(player, &bPlayer);
|
|
|
|
|
|
access = bPlayer ? CHANNEL_PLAYER_TRANSMIT : CHANNEL_OBJECT_TRANSMIT;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_TRANSMIT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 13:18:24 -06:00
|
|
|
|
return ((ch->type & access) != 0)
|
|
|
|
|
|
|| channel_lock_ok(m_pIPermissions, player, ch->chan_obj, kA_LUSE);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::test_receive_access(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(player, &bCommAll);
|
|
|
|
|
|
if (bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int access;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(player, &bPlayer);
|
|
|
|
|
|
access = bPlayer ? CHANNEL_PLAYER_RECEIVE : CHANNEL_OBJECT_RECEIVE;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_RECEIVE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 13:18:24 -06:00
|
|
|
|
return ((ch->type & access) != 0)
|
|
|
|
|
|
|| channel_lock_ok(m_pIPermissions, player, ch->chan_obj, kA_LENTER);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
bool CComsysMod::test_join_access(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(player, &bCommAll);
|
|
|
|
|
|
if (bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int access;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(player, &bPlayer);
|
|
|
|
|
|
access = bPlayer ? CHANNEL_PLAYER_JOIN : CHANNEL_OBJECT_JOIN;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_JOIN;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 13:18:24 -06:00
|
|
|
|
return ((ch->type & access) != 0)
|
|
|
|
|
|
|| channel_lock_ok(m_pIPermissions, player, ch->chan_obj, kA_LOCK);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Policy helpers (#1194 / #1195).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::flag_set(dbref obj, int word, unsigned int bit)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
unsigned int flags = 0;
|
|
|
|
|
|
if (MUX_FAILED(m_pIObjectInfo->GetFlags(obj, word, &flags)))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (flags & bit) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::is_wizard(dbref obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool b = false;
|
|
|
|
|
|
m_pIPermissions->IsWizard(obj, &b);
|
|
|
|
|
|
return b;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool b = false;
|
|
|
|
|
|
m_pIObjectInfo->IsWizard(obj, &b);
|
|
|
|
|
|
return b;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::is_gagged(dbref obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
return flag_set(obj, MOD_FLAG_WORD2, MOD_FLAG_GAGGED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::is_hidden(dbref obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Engine Hidden(x) is (Flags(x) & DARK) — FLAG_WORD1.
|
|
|
|
|
|
//
|
|
|
|
|
|
return flag_set(obj, MOD_FLAG_WORD1, MOD_FLAG_DARK);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::is_guest(dbref obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
unsigned int powers = 0;
|
|
|
|
|
|
if (MUX_FAILED(m_pIObjectInfo->GetPowers(obj, &powers)))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (powers & MOD_POW_GUEST) != 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::undead_connected(dbref obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Engine UNDEAD(x): Good_obj && (not player || Connected).
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr == m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
m_pIObjectInfo->IsValid(obj, &bValid);
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(obj, &bPlayer);
|
|
|
|
|
|
if (!bPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bConnected = false;
|
|
|
|
|
|
m_pIObjectInfo->IsConnected(obj, &bConnected);
|
|
|
|
|
|
return bConnected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::pay_channel_charge(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == ch || 0 >= ch->charge)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const int cost = is_guest(player) ? 0 : ch->charge;
|
|
|
|
|
|
bool bPaid = false;
|
|
|
|
|
|
if (MUX_FAILED(m_pIObjectInfo->PayFor(player, cost, &bPaid)) || !bPaid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
ch->amount_col += ch->charge;
|
|
|
|
|
|
sqlite_wt_channel(ch);
|
|
|
|
|
|
m_pIObjectInfo->GiveTo(ch->charge_who, ch->charge);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CComsysMod::blocked_by_mogrify(dbref player, struct channel *ch,
|
|
|
|
|
|
const UTF8 *arg2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Engine call_mogrifier(MOGRIFY`BLOCK): non-empty result suppresses send.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( nullptr == ch
|
|
|
|
|
|
|| ch->chan_obj < 0
|
|
|
|
|
|
|| nullptr == m_pIAttributeAccess
|
|
|
|
|
|
|| nullptr == m_pIEvaluator
|
|
|
|
|
|
|| nullptr == arg2)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 atext[MOD_LBUF_SIZE];
|
|
|
|
|
|
atext[0] = '\0';
|
|
|
|
|
|
size_t nLen = 0;
|
|
|
|
|
|
// GOD executor so AF_DARK channel-object attrs are readable.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT mr = m_pIAttributeAccess->GetAttribute(1, ch->chan_obj,
|
|
|
|
|
|
T("MOGRIFY`BLOCK"), atext, sizeof(atext) - 1, &nLen);
|
|
|
|
|
|
if (MUX_FAILED(mr) || 0 == nLen || '\0' == atext[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 chattype[2];
|
|
|
|
|
|
chattype[0] = (':' == arg2[0] || ';' == arg2[0]) ? arg2[0] : '"';
|
|
|
|
|
|
chattype[1] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 sdrBuf[32];
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"), player);
|
2026-07-25 13:43:00 -06:00
|
|
|
|
|
|
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetName(player, &pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
pName = T("???");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *block_args[5] = {
|
|
|
|
|
|
chattype, ch->name, arg2, pName, sdrBuf
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 result[MOD_LBUF_SIZE];
|
|
|
|
|
|
result[0] = '\0';
|
|
|
|
|
|
size_t nResult = 0;
|
|
|
|
|
|
mr = m_pIEvaluator->EvalWithArgs(ch->chan_obj, ch->chan_obj, player,
|
|
|
|
|
|
atext, block_args, 5, result, sizeof(result), &nResult);
|
|
|
|
|
|
if (MUX_FAILED(mr) || 0 == nResult || '\0' == result[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player, result);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Unsubscribe a player from a channel completely.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_delcomchannel(dbref player, const UTF8 *channel,
|
|
|
|
|
|
bool bQuiet)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(channel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Order matters, and it is the engine's (comsys.cpp:2591). Clearing
|
|
|
|
|
|
// bConnected FIRST is why the leaver does not see their own departure
|
|
|
|
|
|
// broadcast -- SendChannelMessage skips disconnected users -- and is what
|
|
|
|
|
|
// makes the two implementations agree on the visible result.
|
2026-07-28 02:20:23 +00:00
|
|
|
|
//
|
|
|
|
|
|
do_comdisconnectchannel(player, channel);
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if (!bQuiet)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if ( user->bUserIsOn
|
|
|
|
|
|
&& !is_hidden(player))
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 pfxNormal[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 pfxNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
BuildSpeakerPrefix(ch, user, pfxNormal, sizeof(pfxNormal),
|
|
|
|
|
|
pfxNoComtitle, sizeof(pfxNoComtitle));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 msgNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s has left this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNormal));
|
2026-07-28 12:12:21 -06:00
|
|
|
|
mux_sprintf(msgNoComtitle, sizeof(msgNoComtitle), M_("%s has left this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNoComtitle));
|
|
|
|
|
|
SendChannelMessage(player, ch, msg,
|
|
|
|
|
|
('\0' != pfxNoComtitle[0]) ? msgNoComtitle : nullptr, true);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// The engine gives the leaver a personal confirmation (#1640). The
|
|
|
|
|
|
// module emitted only the channel broadcast, which the leaver cannot
|
|
|
|
|
|
// even see -- so `delcom` looked like it had done nothing, and any
|
|
|
|
|
|
// softcode or client trigger matching "^You have left channel"
|
|
|
|
|
|
// stopped firing under the module.
|
2026-07-28 02:20:23 +00:00
|
|
|
|
//
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You have left channel %s."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(channel));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
ch->users.erase(player);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
sqlite_wt_delete_channel_user(channel, player);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
// Sort aliases for display/lookup.
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
void CComsysMod::sort_com_aliases(comsys_t &c)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
std::sort(c.aliases.begin(), c.aliases.end(),
|
|
|
|
|
|
[](const com_alias &a, const com_alias &b)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
return a.alias < b.alias;
|
|
|
|
|
|
});
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Channel message broadcasting.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Speaker prefix, with the comtitle the engine has always shown (#1640).
|
|
|
|
|
|
//
|
|
|
|
|
|
// The module never applied comtitles to anything: not speech, not poses, not
|
|
|
|
|
|
// join/leave, and not spoof channels -- where the comtitle is supposed to
|
|
|
|
|
|
// REPLACE the name entirely. Measured against the engine on identical
|
|
|
|
|
|
// databases, `comtitle d=Tester` then `d hello`:
|
|
|
|
|
|
//
|
|
|
|
|
|
// engine HDR Tester Wizard says, "hello"
|
|
|
|
|
|
// module HDR Wizard says, "hello"
|
|
|
|
|
|
//
|
|
|
|
|
|
// The module stored the title correctly and round-tripped it through
|
|
|
|
|
|
// SyncChannelUser; it simply never read it back out when composing a message.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Two variants because the LISTENER decides, not the speaker: a player with
|
|
|
|
|
|
// comtitles off sees the plain name even when the speaker has one set. On a
|
|
|
|
|
|
// spoof channel the engine builds no second variant, so everyone sees the
|
|
|
|
|
|
// comtitle -- pNoComtitle stays empty and SendChannelMessage falls back.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Follows the engine's BuildChannelMessage (comsys.cpp:1349) branch for
|
|
|
|
|
|
// branch, including that ComTitleStatus is ignored when the channel is spoof.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::BuildSpeakerPrefix(struct channel *ch,
|
|
|
|
|
|
const struct comuser *user, UTF8 *pNormal, size_t nNormal,
|
|
|
|
|
|
UTF8 *pNoComtitle, size_t nNoComtitle)
|
|
|
|
|
|
{
|
|
|
|
|
|
pNormal[0] = '\0';
|
|
|
|
|
|
pNoComtitle[0] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
const bool bSpoof = ((ch->type & CHANNEL_SPOOF) != 0);
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *pMoniker = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(user->who, &pMoniker);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pMoniker)
|
|
|
|
|
|
{
|
|
|
|
|
|
pMoniker = T("???");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool bHasComTitle = !user->title.empty();
|
|
|
|
|
|
if ( !bHasComTitle
|
|
|
|
|
|
|| (!user->ComTitleStatus && !bSpoof))
|
|
|
|
|
|
{
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(pNormal, nNormal, T("%s %s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(pMoniker));
|
|
|
|
|
|
if (!bSpoof)
|
|
|
|
|
|
{
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(pNoComtitle, nNoComtitle, T("%s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pNormal));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The engine evaluates the comtitle as softcode unless `eval_comtitle 0`
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
// (mudconf.eval_comtitle, default on). Since #1654 the flag reaches the
|
|
|
|
|
|
// module through mux_IGameConfig, queried per call rather than cached --
|
|
|
|
|
|
// a boot-time snapshot is #1613's bug. Against an engine without the
|
|
|
|
|
|
// interface, fall back to the default (evaluate), which is the old
|
|
|
|
|
|
// behaviour.
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
//
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
bool bEvalComtitle = true;
|
|
|
|
|
|
if (nullptr != m_pIGameConfig)
|
|
|
|
|
|
{
|
|
|
|
|
|
GAME_CONFIG gc;
|
|
|
|
|
|
memset(&gc, 0, sizeof(gc));
|
|
|
|
|
|
gc.cbSize = sizeof(gc);
|
|
|
|
|
|
if (MUX_SUCCEEDED(m_pIGameConfig->GetGameConfig(&gc)))
|
|
|
|
|
|
{
|
|
|
|
|
|
bEvalComtitle = gc.eval_comtitle;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 title[MOD_LBUF_SIZE];
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(title, sizeof(title), T("%s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
user->title.c_str());
|
feat(modules): GAME_CONFIG -- game-policy config reaches modules (#1654)
The parking condition on #1654 was "a second consumer"; the tally reached
three, all documented divergences:
searchcost mail non-wizard self @mail/stats was free
eval_comtitle comsys comtitles always evaluated under the module
money_name_* mail the charge-refusal text could not be composed
## Shape
A new engine-registered class rather than an Initialize signature change:
mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig
No existing IID moves, so there is no ABI break in either direction: an old
module never asks, and a new module against an old engine gets
CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour.
Two design rules baked in rather than documented and hoped for:
* Queried per CALL, never cached at Initialize. A boot-time snapshot is
#1613's bug -- @admin reports Set. while the module keeps stale values.
@admin search_cost=50 now takes effect on the next @mail/stats.
* Versioned by cbSize: caller zeroes the struct and sets cbSize; the
engine fills what fits. The struct can grow without a new interface,
and zero must stay a safe default for every future field.
## Both consumers, verified as a mortal
The gap was wizard-invisible -- payfor() exempts wizards and every harness
runs as God -- so verification used muxscript -p with a @pcreate'd mortal:
search_cost 5, rich engine 150->145 module 150->145 IDENTICAL
search_cost 99999, poor both: "Finding mail stats costs 99999
Pennies.", money untouched
eval_comtitle default [ec] 3 Wizard says ... both sides
eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides
The eval_comtitle rows close the divergence documented at
channel_speaker_name since #1640/#1647.
Conformance and smoke are unchanged by construction (wizard runs), and were
run anyway.
make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0.
Refs #1613, #1614, #1631, #1640, #1647. Closes #1654.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:35 -06:00
|
|
|
|
if ( bEvalComtitle
|
|
|
|
|
|
&& nullptr != m_pIEvaluator)
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
{
|
|
|
|
|
|
UTF8 evaled[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nOut = 0;
|
|
|
|
|
|
if (MUX_SUCCEEDED(m_pIEvaluator->EvalWithArgs(user->who, user->who,
|
|
|
|
|
|
user->who, title, nullptr, 0, evaled, sizeof(evaled) - 1,
|
|
|
|
|
|
&nOut)))
|
|
|
|
|
|
{
|
|
|
|
|
|
evaled[nOut] = '\0';
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(title, sizeof(title), T("%s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(evaled));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (bSpoof)
|
|
|
|
|
|
{
|
|
|
|
|
|
// The comtitle stands in for the speaker.
|
|
|
|
|
|
//
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(pNormal, nNormal, T("%s %s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(title));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(pNormal, nNormal, T("%s %s %s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(title),
|
|
|
|
|
|
reinterpret_cast<const char *>(pMoniker));
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(pNoComtitle, nNoComtitle, T("%s %s"),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(pMoniker));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
void CComsysMod::SendChannelMessage(dbref executor, struct channel *ch,
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
const UTF8 *msg, const UTF8 *msgNoComtitle, bool bJoinLeaveMsg)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
|
|
|
|
|
ch->num_messages++;
|
|
|
|
|
|
sqlite_wt_channel(ch);
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
// MOGRIFY`MESSAGE / `OVERRIDE / `FORMAT, and per-player CHATFORMAT
|
|
|
|
|
|
// (#1572). Only BLOCK and NOBUFFER were implemented here; the other
|
|
|
|
|
|
// three hooks and CHATFORMAT were silently ignored, so a channel
|
|
|
|
|
|
// configured with them behaved differently depending on which
|
|
|
|
|
|
// implementation happened to be live.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Order and precedence follow the engine exactly (comsys.cpp:1705-1822),
|
|
|
|
|
|
// because "mostly the same" is what produced the divergence in the first
|
|
|
|
|
|
// place.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 sdrBuf[32];
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"),
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
static_cast<int>(executor));
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 mogMsg[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 mogFmt[MOD_LBUF_SIZE];
|
|
|
|
|
|
mogMsg[0] = '\0';
|
|
|
|
|
|
mogFmt[0] = '\0';
|
|
|
|
|
|
bool bMogOverride = false;
|
|
|
|
|
|
|
|
|
|
|
|
// The engine evaluates these only for non-join/leave traffic
|
|
|
|
|
|
// (comsys.cpp:1701).
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!bJoinLeaveMsg)
|
|
|
|
|
|
{
|
|
|
|
|
|
const UTF8 *mog_args[3] = { ch->name, msg, sdrBuf };
|
|
|
|
|
|
|
|
|
|
|
|
mogrify_eval(ch, executor, T("MESSAGE"), mog_args, 3,
|
|
|
|
|
|
mogMsg, sizeof(mogMsg));
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 ovr[MOD_LBUF_SIZE];
|
|
|
|
|
|
if (mogrify_eval(ch, executor, T("OVERRIDE"), mog_args, 3,
|
|
|
|
|
|
ovr, sizeof(ovr)))
|
|
|
|
|
|
{
|
|
|
|
|
|
bMogOverride = mogrify_truthy(ovr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// FORMAT sees the message AFTER MESSAGE may have replaced it.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *fmt_msg = ('\0' != mogMsg[0]) ? mogMsg : msg;
|
|
|
|
|
|
const UTF8 *fmt_args[3] = { ch->name, fmt_msg, sdrBuf };
|
|
|
|
|
|
mogrify_eval(ch, executor, T("FORMAT"), fmt_args, 3,
|
|
|
|
|
|
mogFmt, sizeof(mogFmt));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *effMsg = ('\0' != mogMsg[0]) ? mogMsg : msg;
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// The no-comtitle variant follows the same mogrify substitution the
|
|
|
|
|
|
// normal one does (engine comsys.cpp:1766-1767): a MOGRIFY`MESSAGE
|
|
|
|
|
|
// result replaces both, because it replaced the whole line.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *effMsgNoComtitle = nullptr;
|
|
|
|
|
|
if ( nullptr != msgNoComtitle
|
|
|
|
|
|
&& '\0' != msgNoComtitle[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
effMsgNoComtitle = ('\0' != mogMsg[0]) ? mogMsg : msgNoComtitle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : ch->users)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser &user = kv.second;
|
|
|
|
|
|
if (bJoinLeaveMsg && user.bGagJoinLeave)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (user.bConnected && user.bUserIsOn
|
|
|
|
|
|
&& test_receive_access(user.who, ch))
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Whether a comtitle is shown is the LISTENER's setting, not the
|
|
|
|
|
|
// speaker's. On a spoof channel there is no second variant.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *pMsg = ( user.ComTitleStatus
|
|
|
|
|
|
|| nullptr == effMsgNoComtitle)
|
|
|
|
|
|
? effMsg : effMsgNoComtitle;
|
|
|
|
|
|
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
// Channel-wide FORMAT wins outright when OVERRIDE is set.
|
|
|
|
|
|
//
|
|
|
|
|
|
if ('\0' != mogFmt[0] && bMogOverride)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(user.who, mogFmt);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Per-player CHATFORMAT, unless OVERRIDE suppresses it.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!bMogOverride && nullptr != m_pIEvaluator)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 chatfmt[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nFmt = 0;
|
|
|
|
|
|
if (MUX_SUCCEEDED(m_pIAttributeAccess->GetAttribute(
|
|
|
|
|
|
user.who, user.who, T("CHATFORMAT"),
|
|
|
|
|
|
chatfmt, sizeof(chatfmt) - 1, &nFmt))
|
|
|
|
|
|
&& 0 < nFmt)
|
|
|
|
|
|
{
|
|
|
|
|
|
chatfmt[nFmt] = '\0';
|
|
|
|
|
|
if ('\0' != chatfmt[0])
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
const UTF8 *cfa[3] = { ch->name, pMsg, sdrBuf };
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
UTF8 fmtbuf[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nOut = 0;
|
|
|
|
|
|
if (MUX_SUCCEEDED(m_pIEvaluator->EvalWithArgs(
|
|
|
|
|
|
user.who, user.who, executor, chatfmt,
|
|
|
|
|
|
cfa, 3, fmtbuf, sizeof(fmtbuf) - 1, &nOut)))
|
|
|
|
|
|
{
|
|
|
|
|
|
fmtbuf[nOut] = '\0';
|
|
|
|
|
|
m_pINotify->RawNotify(user.who, fmtbuf);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ('\0' != mogFmt[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(user.who, mogFmt);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
m_pINotify->RawNotify(user.who, pMsg);
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
}
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
|
2026-07-27 18:18:43 +00:00
|
|
|
|
// Engine only evaluates MOGRIFY`* for non-join/leave traffic
|
|
|
|
|
|
// (comsys.cpp:1701). Pass that through so join/leave history is not
|
|
|
|
|
|
// suppressed by a sticky NOBUFFER=1 under the module alone.
|
|
|
|
|
|
//
|
|
|
|
|
|
RecordChannelHistory(executor, ch, msg, bJoinLeaveMsg);
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Append a message to the channel's recall buffer (#1564).
|
|
|
|
|
|
//
|
|
|
|
|
|
// The engine's SendChannelMessage writes HISTORY_n at comsys.cpp:1851. The
|
|
|
|
|
|
// module never did, so do_crecall above read HISTORY_n attributes that
|
|
|
|
|
|
// nothing wrote: recall came back empty on a channel with obvious traffic,
|
|
|
|
|
|
// with cmsgs and cbuffer both reporting healthy values because those come
|
|
|
|
|
|
// from elsewhere. Silent, and invisible to any test that did not read the
|
|
|
|
|
|
// history back.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The index must be num_messages % logmax computed AFTER the increment in
|
|
|
|
|
|
// the caller, which is exactly what do_crecall's read expects.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Join and leave messages are logged too. The engine guards its logging
|
|
|
|
|
|
// block only on the channel object being valid, not on bJoinLeaveMsg, and
|
|
|
|
|
|
// its recall output does include "has joined this channel".
|
|
|
|
|
|
//
|
|
|
|
|
|
// GOD (dbref 1) is the executor, matching both the engine -- which writes
|
|
|
|
|
|
// with atr_add(..., GOD, ...) -- and this module's existing use of a GOD
|
|
|
|
|
|
// executor for channel-object attributes. The speaker is an ordinary
|
|
|
|
|
|
// member who will not usually control the channel object, so passing the
|
|
|
|
|
|
// executor through would fail bCanSetAttr for most messages.
|
|
|
|
|
|
//
|
2026-07-27 15:48:18 +00:00
|
|
|
|
// LOG_TIMESTAMPS (#1570): when the attribute is present and non-empty on the
|
|
|
|
|
|
// channel object, wrap the line as the engine does -- "[%s] %s" with
|
|
|
|
|
|
// CLinearTimeAbsolute::ReturnDateString(0). libmux exports that class, so
|
|
|
|
|
|
// the module can match the engine's format rather than inventing a second
|
|
|
|
|
|
// one with strftime. @cset/timestamp_logs sets or clears the attribute;
|
|
|
|
|
|
// without this consult it reported success while history stayed plain.
|
|
|
|
|
|
//
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
// MOGRIFY`NOBUFFER (#1572).
|
|
|
|
|
|
//
|
|
|
|
|
|
// The engine evaluates the hook with three arguments -- channel name, message,
|
|
|
|
|
|
// and the sender as "#<dbref>" -- and skips the recall buffer when the result
|
|
|
|
|
|
// is true by xlate() (comsys.cpp:1727). The module can do the same: it already
|
|
|
|
|
|
// holds mux_IEvaluator, whose EvalWithArgs exists for exactly this (the
|
|
|
|
|
|
// interface comment cites MOGRIFY`BLOCK, #1194).
|
|
|
|
|
|
//
|
2026-07-27 18:18:43 +00:00
|
|
|
|
// Evaluated via EvalWithArgs (same path as blocked_by_mogrify), with xlate-
|
|
|
|
|
|
// style truth on the result so "0" and empty stay off. A hook added now
|
|
|
|
|
|
// should match the engine rather than invent a second meaning.
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
//
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
// Evaluate MOGRIFY`<suffix> on the channel object (#1572).
|
|
|
|
|
|
//
|
|
|
|
|
|
// The engine's call_mogrifier (comsys.cpp:1307) reads the attribute and runs
|
|
|
|
|
|
// it with mux_exec as the channel object, with the speaker as enactor. This
|
|
|
|
|
|
// is that, through the module's evaluator interface. A GOD executor is used
|
|
|
|
|
|
// for the attribute READ so AF_DARK channel-object attributes are visible --
|
|
|
|
|
|
// the speaker is an ordinary member and usually cannot see them.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool CComsysMod::mogrify_eval(struct channel *ch, dbref executor,
|
|
|
|
|
|
const UTF8 *suffix, const UTF8 *args[], int nargs,
|
|
|
|
|
|
UTF8 *out, size_t outsz)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != out && 0 < outsz)
|
|
|
|
|
|
{
|
|
|
|
|
|
out[0] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( nullptr == ch
|
|
|
|
|
|
|| ch->chan_obj < 0
|
|
|
|
|
|
|| nullptr == out
|
|
|
|
|
|
|| nullptr == m_pIAttributeAccess
|
|
|
|
|
|
|| nullptr == m_pIEvaluator)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 aname[64];
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(aname, sizeof(aname), T("MOGRIFY`%s"),
|
fix(comsys): the module honours all five MOGRIFY hooks and CHATFORMAT (#1572)
The module implemented BLOCK and NOBUFFER. MESSAGE, OVERRIDE and FORMAT
were silently ignored, and per-player CHATFORMAT was not present at all,
so a channel configured with any of them behaved differently depending on
which implementation happened to be live. Silence rather than an error,
which is why 31 comsys cases in the corpus passed identically against
both.
Two of the issue's claims were stale and are corrected here: BLOCK already
evaluated through the evaluator interface rather than comparing raw
attribute text, and NOBUFFER had landed. The gap was three hooks and
CHATFORMAT, not four hooks and a semantics bug.
Order and precedence follow comsys.cpp:1705-1822 exactly. "Mostly the
same" is what produced the divergence in the first place, so MESSAGE
replaces the text, FORMAT sees the result of MESSAGE, OVERRIDE both
suppresses CHATFORMAT and lets FORMAT win outright, and history still
records the UN-mogrified message as the engine does.
## An engine bug fell out of it
s_chatformat_atr was a static, lazy-initialised ONCE PER PROCESS.
CHATFORMAT is a vattr, created the first time any player sets one -- so on
a game where nobody had one before the first channel message, the number
cached as 0 and CHATFORMAT was ignored for the entire life of the process,
including for every player who set one afterwards. Restarting fixed it,
which is the shape that never gets reported as a bug.
It survived because nothing compared the implementations. Now it caches
only a hit.
## tests/comsys_mogrify, and why it is not in comsys_handoff
Five cases, each driving both implementations and asserting they agree.
4 of 5 fail against the unfixed code, and case 3 fails on the ENGINE side,
which is what pins the cache fix.
It is a separate harness because comsys_handoff cannot test delivery.
That driver's shape is "establish state under one implementation, read it
under the other", and bConnected is runtime state set when a player joins
during that process -- it is not persisted. A run inheriting membership
from an earlier run has user records with bConnected false and delivers to
nobody. Each side must join and speak within one process.
That cost real time and briefly looked like "the module never delivers at
all". It does; verified over a real socket against netmux before drawing
any conclusion. A note in comsys_handoff records why the cases are not
there, so nobody adds them back.
Case 5 is weak by construction and kept deliberately: it asserts
CHATFORMAT is ABSENT under OVERRIDE, which is trivially true when
CHATFORMAT never works at all, so it passes in both columns. It earns its
place only once the others pass, where it distinguishes OVERRIDE from
FORMAT alone.
make test green: 1561/1561 on all three smoke routes, handoff 13/13,
mogrify 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:53:36 -06:00
|
|
|
|
reinterpret_cast<const char *>(suffix));
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 expr[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nExpr = 0;
|
|
|
|
|
|
if (MUX_FAILED(m_pIAttributeAccess->GetAttribute(1, ch->chan_obj, aname,
|
|
|
|
|
|
expr, sizeof(expr) - 1, &nExpr))
|
|
|
|
|
|
|| 0 == nExpr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
expr[nExpr] = '\0';
|
|
|
|
|
|
if ('\0' == expr[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t nResult = 0;
|
|
|
|
|
|
if (MUX_FAILED(m_pIEvaluator->EvalWithArgs(ch->chan_obj, ch->chan_obj,
|
|
|
|
|
|
executor, expr, args, nargs, out, outsz - 1, &nResult)))
|
|
|
|
|
|
{
|
|
|
|
|
|
out[0] = '\0';
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
out[nResult] = '\0';
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Approximate xlate() (functions.cpp) for the common 0/1/empty results
|
|
|
|
|
|
// without pulling engine-private xlate into the module. Leading "#-" is
|
|
|
|
|
|
// false; bare "#N" is true; "0" is false; other non-empty is true.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool CComsysMod::mogrify_truthy(const UTF8 *result)
|
|
|
|
|
|
{
|
|
|
|
|
|
const char *p = reinterpret_cast<const char *>(result);
|
|
|
|
|
|
while (' ' == *p) p++;
|
|
|
|
|
|
if ('\0' == *p) return false;
|
|
|
|
|
|
if ('#' == *p) return ('-' != p[1]);
|
|
|
|
|
|
if ('0' == p[0] && '\0' == p[1]) return false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
bool CComsysMod::nobuffer_by_mogrify(dbref executor, struct channel *ch,
|
|
|
|
|
|
const UTF8 *msg)
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
{
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
if ( nullptr == ch
|
|
|
|
|
|
|| ch->chan_obj < 0
|
|
|
|
|
|
|| nullptr == m_pIAttributeAccess
|
|
|
|
|
|
|| nullptr == m_pIEvaluator
|
|
|
|
|
|
|| nullptr == msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 expr[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nExpr = 0;
|
|
|
|
|
|
MUX_RESULT mr = m_pIAttributeAccess->GetAttribute(1, ch->chan_obj,
|
|
|
|
|
|
T("MOGRIFY`NOBUFFER"), expr, sizeof(expr) - 1, &nExpr);
|
|
|
|
|
|
if (MUX_FAILED(mr) || 0 == nExpr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
expr[nExpr] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 sdrBuf[32];
|
2026-07-27 22:40:00 -06:00
|
|
|
|
mux_sprintf(sdrBuf, sizeof(sdrBuf), T("#%d"),
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
static_cast<int>(executor));
|
|
|
|
|
|
const UTF8 *args[3] = { ch->name, msg, sdrBuf };
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 result[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t nResult = 0;
|
2026-07-27 18:18:43 +00:00
|
|
|
|
// Match call_mogrifier / blocked_by_mogrify: evaluate as the channel
|
|
|
|
|
|
// object with the speaker as enactor (comsys.cpp:1342).
|
|
|
|
|
|
//
|
|
|
|
|
|
mr = m_pIEvaluator->EvalWithArgs(ch->chan_obj, ch->chan_obj, executor,
|
|
|
|
|
|
expr, args, 3, result, sizeof(result) - 1, &nResult);
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
if (MUX_FAILED(mr) || 0 == nResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
result[nResult] = '\0';
|
|
|
|
|
|
|
2026-07-27 18:18:43 +00:00
|
|
|
|
// Approximate xlate() (functions.cpp) for the common 0/1/empty results
|
|
|
|
|
|
// without pulling engine-private xlate into the module. Leading "#-" is
|
|
|
|
|
|
// false; bare "#N" is true; "0" is false; other non-empty is true.
|
|
|
|
|
|
//
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
const char *p = reinterpret_cast<const char *>(result);
|
|
|
|
|
|
while (' ' == *p) p++;
|
2026-07-27 18:18:43 +00:00
|
|
|
|
if ('\0' == *p) return false;
|
|
|
|
|
|
if ('#' == *p) return ('-' != p[1]);
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
if ('0' == p[0] && '\0' == p[1]) return false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::RecordChannelHistory(dbref executor, struct channel *ch,
|
2026-07-27 18:18:43 +00:00
|
|
|
|
const UTF8 *msg, bool bJoinLeaveMsg)
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
{
|
|
|
|
|
|
// MOGRIFY`NOBUFFER keeps a line out of the recall buffer (#1572). The
|
|
|
|
|
|
// engine honours it; the module did not, so a channel that suppressed
|
|
|
|
|
|
// history under the engine still recorded it under the module.
|
2026-07-27 18:18:43 +00:00
|
|
|
|
// Join/leave is not evaluated by the engine either (comsys.cpp:1701).
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( !bJoinLeaveMsg
|
|
|
|
|
|
&& nobuffer_by_mogrify(executor, ch, msg))
|
fix(comsys): honour MOGRIFY`NOBUFFER in the module (#1572)
The engine skips the recall buffer when the channel object's
MOGRIFY`NOBUFFER hook evaluates true (comsys.cpp:1727). The module wrote
history unconditionally, so a channel that suppressed a line under the
engine recorded it under the module.
#1572's premise about why the other hooks are hard is wrong, and that is
the more useful half of this change. It says the module "reaches the
engine only through mux_IAttributeAccess, mux_IObjectInfo and mux_INotify,
none of which expose an evaluator", and concludes that MESSAGE/FORMAT/
OVERRIDE need a new module API before they can be implemented.
mux_IEvaluator exists (modules.h:344), the comsys module already holds it
as m_pIEvaluator and acquires it at startup, and its EvalWithArgs is fully
implemented engine-side (engine_com.cpp:1807) -- its interface comment
says it was added for MOGRIFY`BLOCK (#1194). So the remaining hooks are
ordinary work, not a design decision. blocked_by_mogrify holds the
evaluator, guards on it being non-null, and then reads the raw attribute
text without evaluating -- which is exactly the "evaluated softcode versus
literal text" divergence #1572 documents for BLOCK.
This hook is therefore evaluated, not read raw: three arguments (channel
name, message, sender as "#<dbref>") through EvalWithArgs, matching
call_mogrifier, with xlate() parity on the result. A hook added now should
match the engine rather than inherit BLOCK's shortcut.
Measured with the module live -- Linux runs it under the harness (#1581):
&MOGRIFY`NOBUFFER nbobj=1
before the hook is set: recorded
after: suppressed
recall: "<NB> ... has joined" + "line-before-hook" (no line-after-hook)
Negative control: with the gate disabled, line-after-hook reaches history.
make test green, both smoke routes 1560/1560.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:39:35 -06:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
if ( nullptr == m_pIAttributeAccess
|
|
|
|
|
|
|| nullptr == m_pIObjectInfo
|
|
|
|
|
|
|| nullptr == ch
|
|
|
|
|
|
|| nullptr == msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bValid);
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 valbuf[64];
|
|
|
|
|
|
size_t nLen = 0;
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t logmax = 0;
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
MUX_RESULT mr = m_pIAttributeAccess->GetAttribute(1, ch->chan_obj,
|
|
|
|
|
|
T("MAX_LOG"), valbuf, sizeof(valbuf) - 1, &nLen);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && 0 < nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
valbuf[nLen] = '\0';
|
2026-07-28 19:43:03 -06:00
|
|
|
|
logmax = mux_atoi64(valbuf);
|
fix(comsys): the module never wrote channel history, so crecall was empty (#1564)
Red master. Three cases in comsysemit_fn.mux fail wherever the comsys
module is active, and they are right to: crecall genuinely returns nothing
on a channel with obvious traffic.
CComsysMod::SendChannelMessage incremented num_messages, wrote the channel
row, and notified listeners. It never wrote HISTORY_n. The module's own
do_crecall reads HISTORY_n, so recall was reading attributes that nothing
in the module wrote. The engine writes them at comsys.cpp:1851; only the
engine ever did.
That is why the symptom looked contradictory: cmsgs reported messages and
cbuffer reported a buffer, because those come from the channel row and the
MAX_LOG attribute, while the history itself was never recorded. Nothing
logged an error, so it was invisible to anything that did not read the
history back.
RecordChannelHistory mirrors the engine's logging block:
- index is num_messages % logmax computed AFTER the increment, which is
exactly what do_crecall's read expects
- join/leave messages are logged too; the engine guards its block only on
the channel object being valid, not on bJoinLeaveMsg, and its recall
output does include "has joined this channel"
- GOD (dbref 1) is the executor, matching the engine's
atr_add(..., GOD, ...) and this module's existing GOD-executor reads of
channel-object attributes. The speaker is an ordinary member who will
not usually control the channel object, so passing the executor through
would fail bCanSetAttr for most messages.
LOG_TIMESTAMPS is NOT honoured here. The engine wraps history lines in a
timestamp when that attribute is set; the module has no equivalent time
source in reach, so @cset/timestamp_logs still does nothing under the
module. That is a pre-existing second divergence, left as-is rather than
half-implemented, and worth its own issue.
This also fixes the Windows build defect that hid all of it from me. The
four module entry points in comsys_mod.cpp and mail_mod.cpp were declared
extern "C" MUX_RESULT DCL_API mux_Register(void)
with no DCL_EXPORT, unlike exp3 and sqlproxy which have it. ELF exports by
default, so Unix was unaffected; on Windows the DLLs exported nothing at
all, mux_AddModule could never load them, and the fallback to the built-in
engine implementation is silent -- the else branch in
discover_comsys_mail_modules logs nothing. So comsys.dll and mail.dll were
built and shipped but unloadable, and every comsys test I wrote was
validated against the engine path without my knowing a second
implementation existed.
Verified on Windows Server 2022, MSVC 14.51, Release x64:
- dumpbin now shows mux_Register/mux_Unregister/mux_CanUnloadNow/
mux_GetClassObject exported from comsys.dll and mail.dll; before, both
exported nothing
- with "module comsys" in the config the module loads and initializes
- BEFORE the history fix, the Linux failure reproduces exactly here:
cbuffer=20, cmsgs=3, crecall empty, lattr(chanobj) = Created Modified
MAX_LOG with no HISTORY_n
- AFTER: crecall returns the history, cemit text reaches it, and
lattr(chanobj) shows HISTORY_1..HISTORY_4
- smoke on the engine path unchanged: 1532 succeeded, 17 failed, and
comsysemit TC002/TC003/TC006 pass
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:02:30 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (logmax < 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Channel does not log; do_crecall reports the same condition.
|
|
|
|
|
|
//
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 histattr[64];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(histattr, sizeof(histattr),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("HISTORY_%d"), ((ch->num_messages % logmax) + logmax) % logmax);
|
2026-07-27 15:48:18 +00:00
|
|
|
|
|
|
|
|
|
|
const UTF8 *payload = msg;
|
|
|
|
|
|
UTF8 stamped[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 tsattr[64];
|
|
|
|
|
|
size_t nTs = 0;
|
|
|
|
|
|
mr = m_pIAttributeAccess->GetAttribute(1, ch->chan_obj,
|
|
|
|
|
|
T("LOG_TIMESTAMPS"), tsattr, sizeof(tsattr) - 1, &nTs);
|
|
|
|
|
|
// Engine uses atr_get_info (presence); @cset clears by writing "".
|
|
|
|
|
|
// Treat non-empty as on, empty/missing as off.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && 0 < nTs)
|
|
|
|
|
|
{
|
|
|
|
|
|
CLinearTimeAbsolute ltaNow;
|
|
|
|
|
|
ltaNow.GetLocal();
|
|
|
|
|
|
// Same shape as comsys.cpp:1857-1865.
|
|
|
|
|
|
//
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(stamped, sizeof(stamped),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("[%s] %s"),
|
2026-07-27 15:48:18 +00:00
|
|
|
|
reinterpret_cast<const char *>(ltaNow.ReturnDateString(0)),
|
|
|
|
|
|
reinterpret_cast<const char *>(msg));
|
|
|
|
|
|
payload = stamped;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
// Check the write (#1620). SetAttribute is permission-checked, and the
|
|
|
|
|
|
// engine writes these same attributes as GOD with AF_CONST -- which
|
|
|
|
|
|
// bCanSetAttr denies in every branch, God included. So on a game that
|
|
|
|
|
|
// ever ran the built-in comsys, a HISTORY_n slot the engine wrote is
|
|
|
|
|
|
// refused here forever once the ring wraps onto it. num_messages is a
|
|
|
|
|
|
// relational column and keeps counting, so the counter and the history
|
|
|
|
|
|
// silently diverge: exactly #1564's symptom, from a different cause.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This does not make the write land. It makes a lost message say so
|
|
|
|
|
|
// instead of vanishing, which is the difference between #1564 taking an
|
|
|
|
|
|
// investigation and taking a log grep.
|
|
|
|
|
|
//
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
// Through the engine (#1620). The permission-checked route is refused
|
|
|
|
|
|
// by the AF_CONST the engine puts on these, so a slot the engine wrote
|
|
|
|
|
|
// first was frozen against the module forever once the ring wrapped
|
|
|
|
|
|
// onto it -- and num_messages kept counting, which is #1564's symptom
|
|
|
|
|
|
// arriving by a second route.
|
|
|
|
|
|
//
|
|
|
|
|
|
MUX_RESULT mrHist = (nullptr != m_pIStorage)
|
|
|
|
|
|
? m_pIStorage->SetChannelAttr(ch->name, histattr, payload)
|
|
|
|
|
|
: MUX_E_FAIL;
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
if (MUX_FAILED(mrHist) && nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("HIST"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 logbuf[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(logbuf, sizeof(logbuf),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("Comsys module: channel history write refused (%s on #%d, result %d); message not recorded."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(histattr),
|
|
|
|
|
|
static_cast<int>(ch->chan_obj), mrHist);
|
|
|
|
|
|
m_pILog->log_text(logbuf);
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Join/leave channel.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_joinchannel(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1195: enforce membership cap like the engine.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
2026-07-25 13:43:00 -06:00
|
|
|
|
if (static_cast<int>(ch->users.size()) >= MAX_USERS_PER_CHANNEL)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Too many people on channel %s already."),
|
2026-07-25 13:43:00 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser cu;
|
|
|
|
|
|
cu.who = player;
|
|
|
|
|
|
cu.bUserIsOn = true;
|
|
|
|
|
|
cu.ComTitleStatus = true;
|
|
|
|
|
|
cu.bGagJoinLeave = false;
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1195: UNDEAD connection, not "always true".
|
|
|
|
|
|
//
|
|
|
|
|
|
cu.bConnected = undead_connected(player);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto result = ch->users.emplace(player, std::move(cu));
|
|
|
|
|
|
user = &result.first->second;
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
else if (!user->bUserIsOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
user->bUserIsOn = true;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You are already on channel %s."),
|
2026-03-08 12:54:45 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// Engine suppresses join announce for Hidden players.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
2026-07-25 13:43:00 -06:00
|
|
|
|
if (is_hidden(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 pfxNormal[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 pfxNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
BuildSpeakerPrefix(ch, user, pfxNormal, sizeof(pfxNormal),
|
|
|
|
|
|
pfxNoComtitle, sizeof(pfxNoComtitle));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 msgNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s has joined this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNormal));
|
|
|
|
|
|
mux_sprintf(msgNoComtitle, sizeof(msgNoComtitle),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s has joined this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNoComtitle));
|
|
|
|
|
|
SendChannelMessage(player, ch, msg,
|
|
|
|
|
|
('\0' != pfxNoComtitle[0]) ? msgNoComtitle : nullptr, true);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_leavechannel(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You have left channel %s."),
|
2026-03-08 12:54:45 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (user->bUserIsOn)
|
|
|
|
|
|
{
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// Engine suppresses leave announce for Hidden players.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
2026-07-25 13:43:00 -06:00
|
|
|
|
if (!is_hidden(player))
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 pfxNormal[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 pfxNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
BuildSpeakerPrefix(ch, user, pfxNormal, sizeof(pfxNormal),
|
|
|
|
|
|
pfxNoComtitle, sizeof(pfxNoComtitle));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 msgNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s has left this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNormal));
|
2026-07-28 12:12:21 -06:00
|
|
|
|
mux_sprintf(msgNoComtitle, sizeof(msgNoComtitle), M_("%s has left this channel."),
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
reinterpret_cast<const char *>(pfxNoComtitle));
|
|
|
|
|
|
SendChannelMessage(player, ch, msg,
|
|
|
|
|
|
('\0' != pfxNoComtitle[0]) ? msgNoComtitle : nullptr, true);
|
2026-07-25 13:43:00 -06:00
|
|
|
|
}
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
|
|
|
|
|
user->bUserIsOn = false;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Channel who list.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_comwho(dbref player, struct channel *ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pINotify || nullptr == m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1195: mirror engine do_comwho — players vs objects; filter Hidden
|
|
|
|
|
|
// unless Wizard_Who / See_Hidden.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bSeeHidden = false;
|
|
|
|
|
|
bool bWizardWho = false;
|
|
|
|
|
|
m_pIObjectInfo->SeeHidden(player, &bSeeHidden);
|
|
|
|
|
|
m_pIObjectInfo->WizardWho(player, &bWizardWho);
|
|
|
|
|
|
const bool bMaySeeHidden = bSeeHidden || bWizardWho;
|
|
|
|
|
|
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(player, M_("-- Players --"));
|
2026-07-25 13:43:00 -06:00
|
|
|
|
|
|
|
|
|
|
for (auto &kv : ch->users)
|
|
|
|
|
|
{
|
|
|
|
|
|
comuser &user = kv.second;
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(user.who, &bPlayer);
|
|
|
|
|
|
if (!bPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bConnected = false;
|
|
|
|
|
|
m_pIObjectInfo->IsConnected(user.who, &bConnected);
|
|
|
|
|
|
const bool bHidden = is_hidden(user.who);
|
|
|
|
|
|
|
|
|
|
|
|
if (bConnected && (!bHidden || bMaySeeHidden))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (user.bUserIsOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(user.who, &pName);
|
|
|
|
|
|
if (nullptr != pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player, pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!bHidden)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Stale presence — disconnect from this channel's view.
|
|
|
|
|
|
//
|
|
|
|
|
|
do_comdisconnectchannel(user.who, ch->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(player, M_("-- Objects --"));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : ch->users)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
const comuser &user = kv.second;
|
2026-07-25 13:43:00 -06:00
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(user.who, &bPlayer);
|
|
|
|
|
|
if (bPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bGoing = false;
|
|
|
|
|
|
m_pIObjectInfo->IsGoing(user.who, &bGoing);
|
|
|
|
|
|
if (bGoing)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (user.bUserIsOn)
|
2026-03-08 12:54:45 -06:00
|
|
|
|
{
|
|
|
|
|
|
const UTF8 *pName = nullptr;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pIObjectInfo->GetMoniker(user.who, &pName);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
if (nullptr != pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player, pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("-- %s --"), reinterpret_cast<const char *>(ch->name));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:34:09 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Recall channel message history from channel object attributes.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_comlast(dbref player, struct channel *ch, int arg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pINotify || nullptr == m_pIAttributeAccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate the channel object.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (NOTHING != ch->chan_obj && nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel does not have an object."));
|
2026-03-08 13:34:09 -06:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dbref obj = ch->chan_obj;
|
|
|
|
|
|
|
|
|
|
|
|
// Read MAX_LOG attribute to determine logging depth.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 valbuf[64];
|
|
|
|
|
|
size_t nLen = 0;
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t logmax = 0;
|
2026-03-08 13:34:09 -06:00
|
|
|
|
MUX_RESULT mr = m_pIAttributeAccess->GetAttribute(player, obj,
|
|
|
|
|
|
T("MAX_LOG"), valbuf, sizeof(valbuf) - 1, &nLen);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && 0 < nLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
valbuf[nLen] = '\0';
|
2026-07-28 19:43:03 -06:00
|
|
|
|
logmax = mux_atoi64(valbuf);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (logmax < 1)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(player, M_("Channel does not log."));
|
2026-03-08 13:34:09 -06:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (arg < 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
arg = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (arg > logmax)
|
|
|
|
|
|
{
|
|
|
|
|
|
arg = logmax;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int histnum = ch->num_messages - arg;
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s -- Begin Comsys Recall --"),
|
2026-03-08 13:34:09 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
|
|
|
|
|
|
for (int count = 0; count < arg; count++)
|
|
|
|
|
|
{
|
|
|
|
|
|
histnum++;
|
|
|
|
|
|
UTF8 attrname[64];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(attrname, sizeof(attrname),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("HISTORY_%d"), ((histnum % logmax) + logmax) % logmax);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
|
|
|
|
|
|
UTF8 message[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t msgLen = 0;
|
|
|
|
|
|
mr = m_pIAttributeAccess->GetAttribute(player, obj,
|
|
|
|
|
|
attrname, message, sizeof(message) - 1, &msgLen);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && 0 < msgLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
message[msgLen] = '\0';
|
|
|
|
|
|
m_pINotify->RawNotify(player, message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%s -- End Comsys Recall --"),
|
2026-03-08 13:34:09 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Process a channel message (alias dispatch target).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::do_processcom(dbref player, const UTF8 *arg1, UTF8 *arg2)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == arg2 || '\0' == *arg2)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(player, M_("No message."));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Truncate excessively long messages.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (3500 < strlen(reinterpret_cast<const char *>(arg2)))
|
|
|
|
|
|
{
|
|
|
|
|
|
arg2[3500] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(arg1);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Unknown channel %s."),
|
2026-03-08 12:54:45 -06:00
|
|
|
|
reinterpret_cast<const char *>(arg1));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *user = select_user(ch, player);
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You are not listed as on that channel. "
|
2026-03-08 12:54:45 -06:00
|
|
|
|
"Delete this alias and readd."));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle sub-commands.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (0 == strcmp(reinterpret_cast<const char *>(arg2), "on"))
|
|
|
|
|
|
{
|
|
|
|
|
|
do_joinchannel(player, ch);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (0 == strcmp(reinterpret_cast<const char *>(arg2), "off"))
|
|
|
|
|
|
{
|
|
|
|
|
|
do_leavechannel(player, ch);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!user->bUserIsOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You must be on %s to do that."),
|
2026-03-08 12:54:45 -06:00
|
|
|
|
reinterpret_cast<const char *>(arg1));
|
|
|
|
|
|
m_pINotify->RawNotify(player, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (0 == strcmp(reinterpret_cast<const char *>(arg2), "who"))
|
|
|
|
|
|
{
|
|
|
|
|
|
do_comwho(player, ch);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:34:09 -06:00
|
|
|
|
// Handle "last [N]" — channel history recall.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (0 == strncmp(reinterpret_cast<const char *>(arg2), "last", 4)
|
|
|
|
|
|
&& ('\0' == arg2[4]
|
|
|
|
|
|
|| (' ' == arg2[4]
|
|
|
|
|
|
&& '\0' != arg2[5])))
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t nRecall = 10; // DFLT_RECALL_REQUEST
|
2026-03-08 13:34:09 -06:00
|
|
|
|
if (' ' == arg2[4])
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
nRecall = mux_atoi64(arg2 + 5);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int nRecallI = (nRecall > INT_MAX) ? INT_MAX
|
|
|
|
|
|
: (nRecall < 0) ? 0 : static_cast<int>(nRecall);
|
|
|
|
|
|
do_comlast(player, ch, nRecallI);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1194: Gagged (non-wizard) may not speak on channels.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (is_gagged(player) && !is_wizard(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("GAGGED players may not speak on channels."));
|
2026-07-25 13:43:00 -06:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// Check transmit access.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!test_transmit_access(player, ch))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("That channel type cannot be transmitted on."));
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// #1194: channel charge (payfor + giveto to charge_who).
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!pay_channel_charge(player, ch))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(player,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You don’t have enough coins."));
|
2026-07-25 13:43:00 -06:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #1194: MOGRIFY`BLOCK on the channel object.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (blocked_by_mogrify(player, ch, arg2))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Build and send the message. The speaker prefix carries the comtitle
|
|
|
|
|
|
// (#1640); everything after it is unchanged.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 pfxNormal[MOD_LBUF_SIZE];
|
|
|
|
|
|
UTF8 pfxNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
BuildSpeakerPrefix(ch, user, pfxNormal, sizeof(pfxNormal),
|
|
|
|
|
|
pfxNoComtitle, sizeof(pfxNoComtitle));
|
|
|
|
|
|
const bool bHaveNoComtitle = ('\0' != pfxNoComtitle[0]);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 msgNoComtitle[MOD_LBUF_SIZE];
|
|
|
|
|
|
msgNoComtitle[0] = '\0';
|
2026-03-08 12:54:45 -06:00
|
|
|
|
const char *pPose = reinterpret_cast<const char *>(arg2);
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// One format string per shape, applied to both prefixes, so the two
|
|
|
|
|
|
// variants cannot drift apart the way the engine's and the module's did.
|
|
|
|
|
|
//
|
2026-07-27 22:40:00 -06:00
|
|
|
|
const UTF8 *pFmt;
|
2026-03-08 12:54:45 -06:00
|
|
|
|
if (':' == pPose[0])
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Pose: "<prefix> <action>", with the leading space already supplied
|
|
|
|
|
|
// when the pose starts with one.
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
|
|
|
|
|
pPose++;
|
|
|
|
|
|
if (' ' == *pPose)
|
|
|
|
|
|
{
|
|
|
|
|
|
pPose++;
|
2026-07-27 22:40:00 -06:00
|
|
|
|
pFmt = T("%s%s");
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-07-27 22:40:00 -06:00
|
|
|
|
pFmt = T("%s %s");
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (';' == pPose[0])
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Semipose: "<prefix><text>"
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
|
|
|
|
|
pPose++;
|
2026-07-27 22:40:00 -06:00
|
|
|
|
pFmt = T("%s%s");
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Say: "<prefix> says, “<text>”"
|
2026-03-08 12:54:45 -06:00
|
|
|
|
//
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pFmt = M_("%s says, “%s”");
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg), pFmt,
|
|
|
|
|
|
reinterpret_cast<const char *>(pfxNormal), pPose);
|
|
|
|
|
|
if (bHaveNoComtitle)
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_sprintf(msgNoComtitle, sizeof(msgNoComtitle), pFmt,
|
|
|
|
|
|
reinterpret_cast<const char *>(pfxNoComtitle), pPose);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
SendChannelMessage(player, ch, msg,
|
|
|
|
|
|
bHaveNoComtitle ? msgNoComtitle : nullptr, false);
|
2026-03-08 12:54:45 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:49:52 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// mux_IComsysControl implementation.
|
2026-03-08 12:15:19 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
MUX_RESULT CComsysMod::Initialize(mux_IComsysStorage *pStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr == pStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 10:39:15 -06:00
|
|
|
|
m_pIStorage = pStorage;
|
|
|
|
|
|
m_pIStorage->AddRef();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
|
|
|
|
|
// Load all channel data from SQLite.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!LoadChannels())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("LOAD"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module: LoadChannels failed."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!LoadChannelUsers())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("LOAD"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module: LoadChannelUsers failed."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!LoadPlayerChannels())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("LOAD"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module: LoadPlayerChannels failed."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
m_pILog->start_log(&fStarted, LOG_ALWAYS, T("COM"), T("LOAD"));
|
|
|
|
|
|
if (fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module: loaded "));
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pILog->log_number(static_cast<int>(m_channels.size()));
|
2026-03-08 12:49:52 -06:00
|
|
|
|
m_pILog->log_text(T(" channels from SQLite."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
MUX_RESULT CComsysMod::PlayerConnect(dbref player)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(player);
|
|
|
|
|
|
if (it == m_comsys.end())
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = it->second;
|
|
|
|
|
|
|
|
|
|
|
|
// Track which channels we have already connected to (avoid duplicates).
|
|
|
|
|
|
//
|
|
|
|
|
|
std::vector<std::string> seen;
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto &ca : c.aliases)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
bool bFound = false;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (const auto &s : seen)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (s == ca.channel)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
bFound = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bFound)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
seen.push_back(ca.channel);
|
|
|
|
|
|
do_comconnectchannel(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()),
|
|
|
|
|
|
ca.alias);
|
|
|
|
|
|
do_comconnectraw_notify(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::PlayerDisconnect(dbref player)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(player);
|
|
|
|
|
|
if (it == m_comsys.end())
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = it->second;
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> seen;
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto &ca : c.aliases)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
bool bFound = false;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (const auto &s : seen)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (s == ca.channel)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
|
|
|
|
|
bFound = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bFound)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
seen.push_back(ca.channel);
|
|
|
|
|
|
do_comdisconnectchannel(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
|
|
|
|
|
do_comdisconnectraw_notify(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::PlayerNuke(dbref player)
|
|
|
|
|
|
{
|
2026-03-27 18:00:52 -06:00
|
|
|
|
if (player < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:08:44 -06:00
|
|
|
|
// Mirror engine ReleaseAllResources comsys side (#1193):
|
|
|
|
|
|
// disconnect presence, clear aliases/membership, destroy owned
|
|
|
|
|
|
// channels, drop per-player comsys row, and purge SQLite player_channels.
|
2026-03-08 12:49:52 -06:00
|
|
|
|
//
|
2026-07-25 08:08:44 -06:00
|
|
|
|
|
|
|
|
|
|
// 1. Quiet disconnect of channel presence.
|
|
|
|
|
|
//
|
|
|
|
|
|
PlayerDisconnect(player);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Remove all aliases and channel membership for this player.
|
|
|
|
|
|
//
|
|
|
|
|
|
auto itCom = m_comsys.find(player);
|
|
|
|
|
|
if (itCom != m_comsys.end())
|
|
|
|
|
|
{
|
|
|
|
|
|
comsys_t &c = itCom->second;
|
|
|
|
|
|
std::vector<com_alias> aliases = c.aliases;
|
|
|
|
|
|
for (const auto &ca : aliases)
|
|
|
|
|
|
{
|
|
|
|
|
|
do_delcomchannel(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()), true);
|
|
|
|
|
|
sqlite_wt_delete_player_channel(player,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.alias.c_str()));
|
|
|
|
|
|
}
|
|
|
|
|
|
c.aliases.clear();
|
|
|
|
|
|
m_comsys.erase(itCom);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Strip residual membership (joined without a surviving alias).
|
|
|
|
|
|
//
|
|
|
|
|
|
for (auto &kv : m_channels)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = kv.second.get();
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (0 != ch->users.erase(player))
|
|
|
|
|
|
{
|
|
|
|
|
|
sqlite_wt_delete_channel_user(ch->name, player);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Destroy channels owned by this player. SQLite ON DELETE CASCADE
|
|
|
|
|
|
// clears channel_users / player_channels for those channel names; also
|
|
|
|
|
|
// drop other players' in-memory aliases that pointed at them.
|
|
|
|
|
|
//
|
|
|
|
|
|
std::vector<std::string> destroyedNames;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto it = m_channels.begin(); it != m_channels.end(); )
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (player == it->second->charge_who)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
2026-07-25 08:08:44 -06:00
|
|
|
|
destroyedNames.emplace_back(
|
|
|
|
|
|
reinterpret_cast<const char *>(it->second->name));
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (nullptr != m_pIStorage)
|
2026-03-08 12:49:52 -06:00
|
|
|
|
{
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(
|
|
|
|
|
|
m_pIStorage->DeleteChannel(it->second->name),
|
|
|
|
|
|
"DeleteChannel(channel=%s) [owner #%d destroyed]",
|
|
|
|
|
|
reinterpret_cast<const char *>(it->second->name),
|
|
|
|
|
|
static_cast<int>(player));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
it = m_channels.erase(it);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
++it;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:08:44 -06:00
|
|
|
|
if (!destroyedNames.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
for (auto &kv : m_comsys)
|
|
|
|
|
|
{
|
|
|
|
|
|
comsys_t &c = kv.second;
|
|
|
|
|
|
for (auto ait = c.aliases.begin(); ait != c.aliases.end(); )
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bHit = false;
|
|
|
|
|
|
for (const auto &nm : destroyedNames)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ait->channel == nm)
|
|
|
|
|
|
{
|
|
|
|
|
|
bHit = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bHit)
|
|
|
|
|
|
{
|
|
|
|
|
|
sqlite_wt_delete_player_channel(c.who,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ait->alias.c_str()));
|
|
|
|
|
|
ait = c.aliases.erase(ait);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
++ait;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Belt-and-suspenders: remaining player_channels rows for this who.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != m_pIStorage)
|
|
|
|
|
|
{
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->DeleteAllPlayerChannels(player),
|
|
|
|
|
|
"DeleteAllPlayerChannels(who=#%d)", static_cast<int>(player));
|
2026-07-25 08:08:44 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:06:40 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// allcom — send command to all channels.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::AllCom(dbref executor, const UTF8 *pAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char *a = reinterpret_cast<const char *>(pAction);
|
|
|
|
|
|
if (0 != strcmp(a, "who") && 0 != strcmp(a, "on") && 0 != strcmp(a, "off"))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Only options available are: on, off and who."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(executor);
|
|
|
|
|
|
if (it == m_comsys.end())
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = it->second;
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto &ca : c.aliases)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
do_processcom(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
const_cast<UTF8 *>(pAction));
|
|
|
|
|
|
if (0 == strcmp(a, "who") && nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor, T(""));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// comlist — list player's channel aliases.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ComList(dbref executor, const UTF8 *pPattern)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 09:03:14 -06:00
|
|
|
|
// comlist schema (#1667 Phase 4 A3) — same as engine do_comlist.
|
|
|
|
|
|
//
|
|
|
|
|
|
static const size_t kComlistAliasCols = 15;
|
|
|
|
|
|
static const size_t kComlistChannelCols = 18;
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 header[256];
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Alias"), kComlistAliasCols);
|
2026-07-28 09:03:14 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel"), kComlistChannelCols);
|
2026-07-28 09:03:14 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos,
|
|
|
|
|
|
reinterpret_cast<const char *>(M_("Status")));
|
2026-07-28 09:03:14 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos,
|
|
|
|
|
|
reinterpret_cast<const char *>(M_("Title")));
|
2026-07-28 09:03:14 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, header);
|
|
|
|
|
|
}
|
2026-03-08 13:06:40 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(executor);
|
|
|
|
|
|
if (it == m_comsys.end())
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("-- End of comlist --"));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = it->second;
|
2026-03-08 13:06:40 -06:00
|
|
|
|
bool bWild = (nullptr != pPattern && '\0' != *pPattern);
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (const auto &ca : c.aliases)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
struct channel *ch = select_channel(
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
struct comuser *user = (nullptr != ch)
|
|
|
|
|
|
? select_user(ch, executor) : nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != user)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (bWild && std::string::npos == ca.channel.find(
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(pPattern)))
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 msg[256];
|
2026-07-27 22:06:07 -06:00
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos,
|
2026-07-28 09:03:14 -06:00
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.alias.c_str()),
|
|
|
|
|
|
kComlistAliasCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos,
|
2026-07-28 09:03:14 -06:00
|
|
|
|
reinterpret_cast<const UTF8 *>(ca.channel.c_str()),
|
|
|
|
|
|
kComlistChannelCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
|
|
|
|
|
if (pos < sizeof(msg))
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_sprintf(msg + pos, sizeof(msg) - pos,
|
|
|
|
|
|
T("%s %s%s %s"),
|
|
|
|
|
|
user->bUserIsOn ? T("on ") : T("off"),
|
|
|
|
|
|
user->ComTitleStatus ? T("con ") : T("coff"),
|
|
|
|
|
|
user->bGagJoinLeave ? T(" gag") : T(""),
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(user->title.c_str()));
|
|
|
|
|
|
}
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Bad Comsys Alias: %s for Channel: %s"),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
ca.alias.c_str(),
|
|
|
|
|
|
ca.channel.c_str());
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("-- End of comlist --"));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// comtitle — set/get comtitle for a channel alias.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ComTitle(dbref executor, const UTF8 *pAlias,
|
|
|
|
|
|
const UTF8 *pTitle, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pAlias || '\0' == *pAlias)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Need an alias to do comtitle."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *chName = get_channel_from_alias(executor, pAlias);
|
|
|
|
|
|
if ('\0' == chName[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("Unknown alias."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(chName);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Illegal comsys alias, please delete."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *user = select_user(ch, executor);
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 msg[256];
|
|
|
|
|
|
switch (key)
|
|
|
|
|
|
{
|
|
|
|
|
|
case COMTITLE_OFF:
|
|
|
|
|
|
if (0 == (ch->type & CHANNEL_SPOOF))
|
|
|
|
|
|
{
|
|
|
|
|
|
user->ComTitleStatus = false;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Comtitles are now off for channel %s"),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You can not turn off comtitles on that channel."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case COMTITLE_ON:
|
|
|
|
|
|
user->ComTitleStatus = true;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Comtitles are now on for channel %s"),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case COMTITLE_GAG:
|
|
|
|
|
|
user->bGagJoinLeave = true;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Join/leave messages are now gagged for channel %s"),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case COMTITLE_UNGAG:
|
|
|
|
|
|
user->bGagJoinLeave = false;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Join/leave messages are now ungagged for channel %s"),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
// Set title.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != pTitle && '\0' != *pTitle)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
user->title.assign(reinterpret_cast<const char *>(pTitle),
|
|
|
|
|
|
0, MAX_TITLE_LEN);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
user->title.clear();
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel_user(ch->name, *user);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Title set to ‘%s’ on channel %s."),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
user->title.c_str(),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// @clist — list channels.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-07-28 08:31:41 -06:00
|
|
|
|
// @clist/full — same schema as engine do_listchannels (#1667 Phase 4 A1).
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ChanListFull(dbref executor, const UTF8 *pPattern)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if (!bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Warning: Only public channels and your channels will be shown."));
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
2026-07-28 08:31:41 -06:00
|
|
|
|
static const size_t kFullNameCols = 13;
|
|
|
|
|
|
static const size_t kFullHeaderCols = 15;
|
|
|
|
|
|
static const size_t kFullOwnerCols = 15;
|
|
|
|
|
|
static const size_t kFullAccessCols = 6;
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 header[256];
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, "*** ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel"), kFullNameCols);
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Header"), kFullHeaderCols);
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Owner"), kFullOwnerCols);
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Access"), kFullAccessCols);
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos,
|
|
|
|
|
|
reinterpret_cast<const char *>(M_("Users")));
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos,
|
|
|
|
|
|
reinterpret_cast<const char *>(M_("Msgs")));
|
2026-07-28 08:31:41 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, header);
|
|
|
|
|
|
}
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
|
|
|
|
|
|
const bool bWild = (nullptr != pPattern && '\0' != *pPattern);
|
|
|
|
|
|
|
|
|
|
|
|
for (auto &kv : m_channels)
|
2026-07-28 02:20:23 +00:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
struct channel *ch = kv.second.get();
|
|
|
|
|
|
|
|
|
|
|
|
if (bWild && nullptr == strstr(
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name),
|
|
|
|
|
|
reinterpret_cast<const char *>(pPattern)))
|
2026-07-28 02:20:23 +00:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
continue;
|
2026-07-28 02:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if (!bCommAll
|
|
|
|
|
|
&& 0 == (ch->type & CHANNEL_PUBLIC))
|
2026-07-28 02:20:23 +00:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
bool bControls = false;
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
2026-07-28 02:20:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who,
|
|
|
|
|
|
&bControls);
|
|
|
|
|
|
}
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if ( !bControls
|
|
|
|
|
|
&& nullptr == select_user(ch, executor))
|
2026-07-28 02:20:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
const UTF8 *pOwnerName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(ch->charge_who, &pOwnerName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pOwnerName)
|
|
|
|
|
|
{
|
|
|
|
|
|
pOwnerName = T("???");
|
|
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// The engine shows "-" for an unset header here, unlike the default
|
|
|
|
|
|
// listing which shows it empty.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *pHeader = ch->header;
|
|
|
|
|
|
if ('\0' == pHeader[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
pHeader = T("-");
|
|
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 line[MOD_LBUF_SIZE];
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
line[pos++] = (ch->type & CHANNEL_PUBLIC) ? 'P' : '-';
|
|
|
|
|
|
line[pos++] = (ch->type & CHANNEL_LOUD) ? 'L' : '-';
|
|
|
|
|
|
line[pos++] = (ch->type & CHANNEL_SPOOF) ? 'S' : '-';
|
|
|
|
|
|
line[pos++] = ' ';
|
|
|
|
|
|
line[pos] = '\0';
|
|
|
|
|
|
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, ch->name,
|
|
|
|
|
|
kFullNameCols);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
pos = append_bytes(line, sizeof(line), pos, " ");
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, pHeader,
|
|
|
|
|
|
kFullHeaderCols);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
pos = append_bytes(line, sizeof(line), pos, " ");
|
2026-07-28 08:31:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, pOwnerName,
|
|
|
|
|
|
kFullOwnerCols);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
pos = append_bytes(line, sizeof(line), pos, " ");
|
|
|
|
|
|
|
2026-07-28 08:31:41 -06:00
|
|
|
|
UTF8 access[4];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
access[0] = test_join_access(executor, ch) ? 'J' : '-';
|
|
|
|
|
|
access[1] = test_transmit_access(executor, ch) ? 'X' : '-';
|
|
|
|
|
|
access[2] = test_receive_access(executor, ch) ? 'R' : '-';
|
|
|
|
|
|
access[3] = '\0';
|
2026-07-28 08:31:41 -06:00
|
|
|
|
// Access is 6 cols (JXR + pad); counts follow with no extra separator.
|
|
|
|
|
|
//
|
|
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, access,
|
|
|
|
|
|
kFullAccessCols);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if (pos < sizeof(line))
|
|
|
|
|
|
{
|
|
|
|
|
|
mux_sprintf(line + pos, sizeof(line) - pos,
|
|
|
|
|
|
T("%5d %4d"), static_cast<int>(ch->users.size()),
|
|
|
|
|
|
ch->num_messages);
|
2026-07-28 02:20:23 +00:00
|
|
|
|
}
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, line);
|
|
|
|
|
|
}
|
2026-07-28 02:20:23 +00:00
|
|
|
|
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("-- End of list of Channels --"));
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ChanList(dbref executor, const UTF8 *pPattern,
|
|
|
|
|
|
int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// @clist/full was silently answered with the DEFAULT listing (#1640):
|
|
|
|
|
|
// Header, Access, Users and Msgs -- the four columns that are the entire
|
|
|
|
|
|
// reason /full exists -- were dropped. /headers was implemented and both
|
|
|
|
|
|
// implementations agreed on it, which is why this looked like a
|
|
|
|
|
|
// formatting difference rather than an unhandled switch.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (key & CLIST_FULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ChanListFull(executor, pPattern);
|
2026-07-28 02:20:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 08:22:41 -06:00
|
|
|
|
// @clist / @clist/headers schema — same as engine do_chanlist
|
|
|
|
|
|
// (#1667 Phase 4 A2): "*** "/PLS prefix, name 13, owner 15, third 45,
|
|
|
|
|
|
// pad to 79. Labels are separate msgids via mux_table_append_ljust.
|
|
|
|
|
|
//
|
|
|
|
|
|
static const size_t kClistNameCols = 13;
|
|
|
|
|
|
static const size_t kClistOwnerCols = 15;
|
|
|
|
|
|
static const size_t kClistThirdCols = 45;
|
|
|
|
|
|
static const size_t kClistLineCols = 79;
|
|
|
|
|
|
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
2026-07-28 08:22:41 -06:00
|
|
|
|
UTF8 header[256];
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, "*** ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel"), kClistNameCols);
|
2026-07-28 08:22:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Owner"), kClistOwnerCols);
|
2026-07-28 08:22:41 -06:00
|
|
|
|
pos = append_bytes(header, sizeof(header), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(header, sizeof(header), pos,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
(key & CLIST_HEADERS) ? M_("Header") : M_("Description"),
|
2026-07-28 08:22:41 -06:00
|
|
|
|
kClistThirdCols);
|
|
|
|
|
|
while (pos < kClistLineCols && pos + 1 < sizeof(header))
|
|
|
|
|
|
{
|
|
|
|
|
|
header[pos++] = ' ';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pos < sizeof(header))
|
|
|
|
|
|
{
|
|
|
|
|
|
header[pos] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
m_pINotify->RawNotify(executor, header);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
bool bWild = (nullptr != pPattern && '\0' != *pPattern);
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : m_channels)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
struct channel *ch = kv.second.get();
|
2026-03-08 13:06:40 -06:00
|
|
|
|
|
|
|
|
|
|
// Simple wildcard filter.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (bWild && nullptr == strstr(
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name),
|
|
|
|
|
|
reinterpret_cast<const char *>(pPattern)))
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Visibility check: public or owner or Comm_All.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bVisible = ((ch->type & CHANNEL_PUBLIC) != 0);
|
|
|
|
|
|
if (!bVisible && nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
bVisible = bCommAll;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bControls = false;
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who,
|
|
|
|
|
|
&bControls);
|
|
|
|
|
|
}
|
|
|
|
|
|
bVisible = bControls;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *pOwnerName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(ch->charge_who, &pOwnerName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pOwnerName)
|
|
|
|
|
|
{
|
|
|
|
|
|
pOwnerName = T("???");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 08:22:41 -06:00
|
|
|
|
const UTF8 *pDesc = (key & CLIST_HEADERS)
|
|
|
|
|
|
? ch->header
|
2026-07-28 12:12:21 -06:00
|
|
|
|
: M_("No description.");
|
2026-03-08 13:06:40 -06:00
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
UTF8 line[MOD_LBUF_SIZE];
|
2026-07-27 22:06:07 -06:00
|
|
|
|
size_t pos = 0;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
line[pos++] = (ch->type & CHANNEL_PUBLIC) ? 'P' : '-';
|
|
|
|
|
|
line[pos++] = (ch->type & CHANNEL_LOUD) ? 'L' : '-';
|
|
|
|
|
|
line[pos++] = (ch->type & CHANNEL_SPOOF) ? 'S' : '-';
|
|
|
|
|
|
line[pos++] = ' ';
|
|
|
|
|
|
line[pos] = '\0';
|
|
|
|
|
|
|
2026-07-28 08:22:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, ch->name,
|
|
|
|
|
|
kClistNameCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(line, sizeof(line), pos, " ");
|
2026-07-28 08:22:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, pOwnerName,
|
|
|
|
|
|
kClistOwnerCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(line, sizeof(line), pos, " ");
|
2026-07-28 08:22:41 -06:00
|
|
|
|
pos = append_ljust_field(line, sizeof(line), pos, pDesc,
|
|
|
|
|
|
kClistThirdCols);
|
|
|
|
|
|
while (pos < kClistLineCols && pos + 1 < sizeof(line))
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
{
|
|
|
|
|
|
line[pos++] = ' ';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pos < sizeof(line))
|
|
|
|
|
|
{
|
|
|
|
|
|
line[pos] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, line);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("-- End of list of Channels --"));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// @cwho — who is on a channel.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ChanWho(dbref executor, const UTF8 *pArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pArg || '\0' == *pArg)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("You must specify a channel."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse "channel/all" format.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 chanName[MAX_CHANNEL_LEN + 1];
|
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
while ('\0' != pArg[i] && '/' != pArg[i] && i < MAX_CHANNEL_LEN)
|
|
|
|
|
|
{
|
|
|
|
|
|
chanName[i] = pArg[i];
|
|
|
|
|
|
i++;
|
|
|
|
|
|
}
|
|
|
|
|
|
chanName[i] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
bool bAll = ('/' == pArg[i] && 'a' == pArg[i + 1]);
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(chanName);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Unknown channel %s."),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(chanName));
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Permission check.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bAllowed = false;
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bAllowed);
|
|
|
|
|
|
if (!bAllowed)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who, &bAllowed);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bAllowed)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("Permission denied."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 09:22:57 -06:00
|
|
|
|
// @cwho schema (#1667 Phase 4 A4) — same as engine do_channelwho.
|
|
|
|
|
|
//
|
|
|
|
|
|
static const size_t kCwhoNameCols = 29;
|
|
|
|
|
|
static const size_t kCwhoStatusCols = 6;
|
|
|
|
|
|
static const size_t kCwhoPlayerCols = 6;
|
|
|
|
|
|
|
2026-03-08 13:06:40 -06:00
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("-- %s --"), reinterpret_cast<const char *>(ch->name));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
|
2026-07-27 22:06:07 -06:00
|
|
|
|
{
|
|
|
|
|
|
size_t pos = 0;
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos, M_("Name"),
|
2026-07-28 09:22:57 -06:00
|
|
|
|
kCwhoNameCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos, M_("Status"),
|
2026-07-28 09:22:57 -06:00
|
|
|
|
kCwhoStatusCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
2026-07-28 12:12:21 -06:00
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos, M_("Player"),
|
2026-07-28 09:22:57 -06:00
|
|
|
|
kCwhoPlayerCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
2026-03-08 13:06:40 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto &kv : ch->users)
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comuser &user = kv.second;
|
2026-03-08 13:06:40 -06:00
|
|
|
|
|
|
|
|
|
|
// If not showing all, only show connected users.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (!bAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bConnected = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pIObjectInfo->IsConnected(user.who, &bConnected);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
if (!bConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 09:22:57 -06:00
|
|
|
|
// UnparseObject for name+dbref+flags (#1640). strip_color so
|
|
|
|
|
|
// width arithmetic stays honest on colored monikers.
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
//
|
|
|
|
|
|
UTF8 unparsed[MOD_LBUF_SIZE];
|
|
|
|
|
|
unparsed[0] = '\0';
|
2026-03-08 13:06:40 -06:00
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
m_pIObjectInfo->UnparseObject(executor, user.who, false,
|
|
|
|
|
|
unparsed, sizeof(unparsed));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
if ('\0' == unparsed[0])
|
2026-03-08 13:06:40 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->GetName(user.who, &pName);
|
|
|
|
|
|
}
|
|
|
|
|
|
mux_sprintf(unparsed, sizeof(unparsed),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("%s"), reinterpret_cast<const char *>(
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
(nullptr != pName) ? pName : T("???")));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bPlayer = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_pIObjectInfo->IsPlayer(user.who, &bPlayer);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-27 22:06:07 -06:00
|
|
|
|
size_t pos = 0;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos,
|
2026-07-28 09:22:57 -06:00
|
|
|
|
strip_color(unparsed), kCwhoNameCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos,
|
2026-07-28 09:22:57 -06:00
|
|
|
|
user.bUserIsOn ? T("on ") : T("off"), kCwhoStatusCols);
|
2026-07-27 22:06:07 -06:00
|
|
|
|
pos = append_bytes(msg, sizeof(msg), pos, " ");
|
|
|
|
|
|
pos = append_ljust_field(msg, sizeof(msg), pos,
|
2026-07-28 09:22:57 -06:00
|
|
|
|
bPlayer ? T("yes") : T("no "), kCwhoPlayerCols);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("-- %s --"), reinterpret_cast<const char *>(ch->name));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// @cemit — emit to a channel.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::CEmit(dbref executor, const UTF8 *pChannel,
|
|
|
|
|
|
const UTF8 *pText, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pChannel || nullptr == pText)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(pChannel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s does not exist."),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Permission check.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bAllowed = false;
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bAllowed);
|
|
|
|
|
|
if (!bAllowed)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who, &bAllowed);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bAllowed)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("Permission denied."));
|
2026-03-08 13:06:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:30:29 -06:00
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
2026-03-08 13:06:40 -06:00
|
|
|
|
if (key == CEMIT_NOHEADER)
|
|
|
|
|
|
{
|
|
|
|
|
|
strncpy(reinterpret_cast<char *>(msg),
|
|
|
|
|
|
reinterpret_cast<const char *>(pText), sizeof(msg) - 1);
|
|
|
|
|
|
msg[sizeof(msg) - 1] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("%s %s"),
|
2026-03-08 13:06:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
reinterpret_cast<const char *>(pText));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// No speaker prefix here: @cemit is the channel talking, not a player,
|
|
|
|
|
|
// so there is no comtitle variant to choose between.
|
|
|
|
|
|
//
|
|
|
|
|
|
SendChannelMessage(executor, ch, msg, nullptr, false);
|
2026-03-08 13:06:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 13:30:29 -06:00
|
|
|
|
MUX_RESULT CComsysMod::CSet(dbref executor, const UTF8 *pChannel,
|
|
|
|
|
|
const UTF8 *pValue, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CSET_LIST == key)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Redirect to ChanList with CLIST_FULL.
|
|
|
|
|
|
//
|
|
|
|
|
|
return ChanList(executor, nullptr, CLIST_FULL);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(pChannel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s does not exist."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
m_pINotify->Notify(executor, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Permission check: must control channel owner or have Comm_All.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bControls = false;
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who, &bControls);
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
if (!bControls && !bCommAll)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->Notify(executor, M_("Permission denied."));
|
2026-03-08 13:30:29 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UTF8 *msg = nullptr;
|
|
|
|
|
|
UTF8 msgbuf[MOD_LBUF_SIZE];
|
|
|
|
|
|
|
|
|
|
|
|
switch (key)
|
|
|
|
|
|
{
|
|
|
|
|
|
case CSET_PUBLIC:
|
|
|
|
|
|
ch->type |= CHANNEL_PUBLIC;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s placed on the public listings."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_PRIVATE:
|
|
|
|
|
|
ch->type &= ~CHANNEL_PUBLIC;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s taken off the public listings."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_LOUD:
|
|
|
|
|
|
ch->type |= CHANNEL_LOUD;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s now sends connect/disconnect msgs."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_QUIET:
|
|
|
|
|
|
ch->type &= ~CHANNEL_LOUD;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s connect/disconnect msgs muted."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_SPOOF:
|
|
|
|
|
|
ch->type |= CHANNEL_SPOOF;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s set spoofable."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_NOSPOOF:
|
|
|
|
|
|
ch->type &= ~CHANNEL_SPOOF;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s set unspoofable."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_OBJECT:
|
|
|
|
|
|
{
|
|
|
|
|
|
dbref thing = NOTHING;
|
|
|
|
|
|
if (nullptr != pValue && '\0' != pValue[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->MatchThing(executor, pValue, &thing);
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (NOTHING != thing)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(thing, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NOTHING == thing)
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->chan_obj = NOTHING;
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s is now disassociated from any channel object."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->chan_obj = thing;
|
|
|
|
|
|
const UTF8 *pName = nullptr;
|
|
|
|
|
|
m_pIObjectInfo->GetName(thing, &pName);
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s is now using %s(#%d) as channel object."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name),
|
|
|
|
|
|
pName ? reinterpret_cast<const char *>(pName) : "???",
|
|
|
|
|
|
thing);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("%d is not a valid channel object."), thing);
|
2026-03-08 13:30:29 -06:00
|
|
|
|
}
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_HEADER:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pValue || '\0' == pValue[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
// Reset to default bold [ChannelName] header.
|
|
|
|
|
|
// COLOR_INTENSE = \xEF\x94\x81, COLOR_RESET = \xEF\x94\x80
|
|
|
|
|
|
//
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(ch->header, sizeof(ch->header),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("\xEF\x94\x81[%s]\xEF\x94\x80"),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
// Optimize/terminate the ANSI, as do_cheader does. The raw
|
|
|
|
|
|
// strncpy that was here stored the header uncollapsed, so
|
|
|
|
|
|
// `@cset/header c=[ansi(r,RED)][ansi(b,BLU)]` persisted an
|
|
|
|
|
|
// extra reset between the two codes under the module and not
|
|
|
|
|
|
// under the engine -- a divergence in SQLite, not merely on
|
|
|
|
|
|
// screen, so it survived a handoff. It also truncated at
|
|
|
|
|
|
// MAX_HEADER_LEN *bytes*, which can split a codepoint or a
|
|
|
|
|
|
// color code in half (#1640, #1649).
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 hdr[MAX_HEADER_LEN + 1];
|
|
|
|
|
|
const mux_field nLen = StripTabsAndTruncate(pValue, hdr,
|
|
|
|
|
|
MAX_HEADER_LEN, MAX_HEADER_LEN);
|
|
|
|
|
|
memcpy(ch->header, hdr, nLen.m_byte + 1);
|
2026-03-08 13:30:29 -06:00
|
|
|
|
}
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>("Set.");
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2026-03-08 13:34:09 -06:00
|
|
|
|
case CSET_LOG:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pValue || '\0' == pValue[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>(
|
|
|
|
|
|
"@cset: Maximum history must be a number less than "
|
|
|
|
|
|
"or equal to 200.");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t value = mux_atoi64(pValue);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
if (value < 0 || value > 200)
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>(
|
|
|
|
|
|
"@cset: Maximum history must be a number less than "
|
|
|
|
|
|
"or equal to 200.");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
// Set when shrinking the ring leaves entries we could not clear
|
|
|
|
|
|
// (#1620); reported with the result rather than swallowed.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bStale = false;
|
|
|
|
|
|
|
2026-03-08 13:34:09 -06:00
|
|
|
|
bool bObjValid = false;
|
|
|
|
|
|
if (NOTHING != ch->chan_obj && nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bObjValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bObjValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>(
|
|
|
|
|
|
"@cset: Channel has no object. Use @cset/object first.");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read old MAX_LOG value.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 oldbuf[64];
|
|
|
|
|
|
size_t nOldLen = 0;
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t oldnum = 0;
|
2026-03-08 13:34:09 -06:00
|
|
|
|
MUX_RESULT mr2 = m_pIAttributeAccess->GetAttribute(
|
|
|
|
|
|
executor, ch->chan_obj, T("MAX_LOG"),
|
|
|
|
|
|
oldbuf, sizeof(oldbuf) - 1, &nOldLen);
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr2) && 0 < nOldLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
oldbuf[nOldLen] = '\0';
|
2026-07-28 19:43:03 -06:00
|
|
|
|
oldnum = mux_atoi64(oldbuf);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If reducing, clear old HISTORY_N attributes.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (value < oldnum)
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
for (int64_t count = 0; count <= oldnum; count++)
|
2026-03-08 13:34:09 -06:00
|
|
|
|
{
|
|
|
|
|
|
UTF8 histattr[64];
|
2026-07-28 19:43:03 -06:00
|
|
|
|
mux_sprintf(histattr, sizeof(histattr), T("HISTORY_%lld"),
|
|
|
|
|
|
static_cast<long long>(count));
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
if (nullptr == m_pIStorage
|
|
|
|
|
|
|| MUX_FAILED(m_pIStorage->SetChannelAttr(ch->name,
|
|
|
|
|
|
histattr, T(""))))
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
{
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
// Engine-written slots used to refuse here (#1620),
|
|
|
|
|
|
// leaving stale history above the new maximum. Going
|
|
|
|
|
|
// through the engine removes that cause; anything
|
|
|
|
|
|
// still failing is a real storage failure and is
|
|
|
|
|
|
// still reported rather than swallowed.
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
//
|
|
|
|
|
|
bStale = true;
|
|
|
|
|
|
}
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set MAX_LOG.
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 vbuf[32];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(vbuf, sizeof(vbuf),
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("%d"), value);
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
if (nullptr == m_pIStorage
|
|
|
|
|
|
|| MUX_FAILED(m_pIStorage->SetChannelAttr(ch->name,
|
|
|
|
|
|
T("MAX_LOG"), vbuf)))
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Failed. Could not set the maximum for %s (the attribute is not writable here)."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (bStale)
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s maximum history set, but older entries could not be cleared."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s maximum history set."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
}
|
2026-03-08 13:34:09 -06:00
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case CSET_LOG_TIME:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pValue || '\0' == pValue[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>("@cset: Failed.");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t value = mux_atoi64(pValue);
|
2026-03-08 13:34:09 -06:00
|
|
|
|
if (value != 0 && value != 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = reinterpret_cast<const UTF8 *>("@cset: Failed.");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool bObjValid = false;
|
|
|
|
|
|
if (NOTHING != ch->chan_obj && nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->chan_obj, &bObjValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bObjValid)
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Failed. Is logging enabled for %s?"),
|
2026-03-08 13:34:09 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verify logging is enabled (MAX_LOG exists).
|
|
|
|
|
|
//
|
|
|
|
|
|
UTF8 logbuf[64];
|
|
|
|
|
|
size_t nLogLen = 0;
|
|
|
|
|
|
MUX_RESULT mr2 = m_pIAttributeAccess->GetAttribute(
|
|
|
|
|
|
executor, ch->chan_obj, T("MAX_LOG"),
|
|
|
|
|
|
logbuf, sizeof(logbuf) - 1, &nLogLen);
|
|
|
|
|
|
if (MUX_FAILED(mr2) || 0 == nLogLen)
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Failed. Is logging enabled for %s?"),
|
2026-03-08 13:34:09 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set or clear LOG_TIMESTAMPS.
|
|
|
|
|
|
//
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
// #1585: this write is refused when the engine set the attribute
|
|
|
|
|
|
// (GOD + AF_CONST, which bCanSetAttr denies in every branch).
|
|
|
|
|
|
// Reporting "set." after a refusal told the player the opposite
|
|
|
|
|
|
// of what happened, and is how the divergence stayed hidden.
|
|
|
|
|
|
//
|
fix(comsys): let the module write engine-owned channel attributes (#1585, #1620)
Both bugs are one refusal. The engine writes HISTORY_%d, MAX_LOG and
LOG_TIMESTAMPS on the channel object as GOD with AF_CONST, and
bCanSetAttr denies AF_CONST in every branch -- God included, which is
unlike every other flag it checks. So the module's permission-checked
SetAttribute is refused, and whichever implementation did not write a
value first can never change it.
#1585 a channel whose timestamps the engine enabled could not be
turned off from the module, and before #1624 the module
reported success anyway.
#1620 the same refusal reaching HISTORY_%d once the ring wraps onto
an engine-written slot. num_messages is a column and keeps
counting, so the counter and the history diverge -- #1564's
symptom arriving by a second route.
mux_IComsysStorage gains SetChannelAttr: the module asks the ENGINE to
perform the write. The engine owns the attribute layer and the
permission check, and here it is writing its own data rather than a
player's, so an attribute add is its operation to make.
Two alternatives were weighed and rejected:
- Dropping AF_CONST from the five comsys writes is the smallest
change, but then anyone controlling the channel object can forge
recall history with a plain @set. Channel logs stop being
tamper-evident, which is what AF_CONST was buying.
- Giving God an escape from AF_CONST in bCanSetAttr is one line and
restores an invariant that holds for every other flag there, but it
is a global permission change to fix one subsystem's problem.
Scope is deliberately narrow. The channel is passed by NAME, so the
engine resolves the object itself and this cannot become "write any
attribute anywhere as GOD". Player-owned attributes are untouched --
mail's Mailcurf/Mailfolders keep going through the permission-checked
mux_IAttributeAccess, which is the right path for anything a player
owns.
tests/comsys_handoff now passes with no TODO markers:
ok 4 - module can clear a flag the engine set (#1585)
ok 6 - module overwrites an engine-written history slot (#1620)
=== comsys handoff: 8 passed, 0 failed, 0 known-failing ===
The harness demanded this edit rather than allowing it: with the fix in
and the markers still present it reported "2 unexpectedly passing" and
failed the run, which is what those markers are for.
One assertion changed direction rather than being deleted. #1624 added
a check that a REFUSED write was reported honestly instead of as
success; there is no refusal left to report, so it now checks that the
clear reports success -- because it now is one. Asserting on the old
failure text would have failed for the right reason in the wrong
direction.
make test green: 1561/1561 on all three smoke routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:26:08 -06:00
|
|
|
|
MUX_RESULT mrTs = (nullptr != m_pIStorage)
|
|
|
|
|
|
? m_pIStorage->SetChannelAttr(ch->name, T("LOG_TIMESTAMPS"),
|
|
|
|
|
|
value ? T("1") : T(""))
|
|
|
|
|
|
: MUX_E_FAIL;
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
if (MUX_FAILED(mrTs))
|
2026-03-08 13:34:09 -06:00
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Failed. Timestamp logging for %s could not be changed (the attribute is not writable here)."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msgbuf, sizeof(msgbuf),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cset: Channel %s timestamp logging set."),
|
fix(comsys,mail): check the nine discarded SetAttribute results (#1620)
mux_IAttributeAccess::SetAttribute is permission-checked, and bCanSetAttr
denies AF_CONST in every branch including God's. The engine writes these
same attributes as GOD with AF_CONST, so on a game that has run the
built-in implementation these writes are refused -- and all nine call
sites across the two modules discarded the result.
Measured consequences, both on macOS with the module asserted live:
- @cset/timestamp_logs told the player "timestamp logging set." after
the write was refused and the value was unchanged. That is #1585,
and reporting success is why it read as a mysterious divergence
rather than a refusal.
- a channel history write refused on ring wrap-around vanished with no
trace, while num_messages (a relational column) kept counting. That
is #1564's counter-vs-history divergence returning by another route.
This does not make the refused writes land -- that needs the storage
decision on #1589. It converts silent data loss into something a log
grep finds, and stops the modules reporting the opposite of what
happened.
comsys: the two @cset paths now report failure instead of success, the
MAX_LOG shrink path reports when old entries could not be cleared, and
the history write logs the attribute, object and result code.
mail: the four Mailcurf/Mailfolders writes go through one checked
helper, since a refused folder write silently reverts the player's
folder list and looks like the command never ran.
Verified under netmux, where the module log is visible:
COM/HIST : Comsys module: channel history write refused
(HISTORY_1 on #12, result -11); message not recorded.
-11 is MUX_E_PERMISSION. Exactly one refusal per ring pass, since only
the engine-written slot is denied -- so this does not flood.
Controls: the module setting and clearing its OWN attribute still
succeeds and still reports success, so the checks do not fire on the
ordinary path.
Known limitation: the module log is not visible under muxscript (the
same gap as #1596), so the harness cannot yet see these. The @cset
messages are visible on both.
make test green: 1561/1561 on both smoke routes, with the modules live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:08:05 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
2026-03-08 13:34:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
msg = msgbuf;
|
|
|
|
|
|
}
|
2026-03-08 13:30:29 -06:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sqlite_wt_channel(ch);
|
|
|
|
|
|
if (nullptr != msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::EditChannel(dbref executor, const UTF8 *pChannel,
|
|
|
|
|
|
const UTF8 *pValue, int flag)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(pChannel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Unknown channel %s."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
m_pINotify->Notify(executor, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Permission check.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bControls = false;
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who, &bControls);
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
if (!bControls && !bCommAll)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->Notify(executor, M_("Permission denied."));
|
2026-03-08 13:30:29 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool add_remove = true;
|
|
|
|
|
|
const UTF8 *s = pValue;
|
|
|
|
|
|
if (nullptr != s && '!' == *s)
|
|
|
|
|
|
{
|
|
|
|
|
|
add_remove = false;
|
|
|
|
|
|
s++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (flag)
|
|
|
|
|
|
{
|
|
|
|
|
|
case EDIT_CHANNEL_CCHOWN:
|
|
|
|
|
|
{
|
|
|
|
|
|
dbref who = NOTHING;
|
|
|
|
|
|
if (nullptr != pValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->MatchThing(executor, pValue, &who);
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (NOTHING != who)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(who, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->charge_who = who;
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("Set."));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("Invalid player."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case EDIT_CHANNEL_CCHARGE:
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
int64_t c_charge = 0;
|
2026-03-08 13:30:29 -06:00
|
|
|
|
if (nullptr != pValue)
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
c_charge = mux_atoi64(pValue);
|
2026-03-08 13:30:29 -06:00
|
|
|
|
}
|
|
|
|
|
|
if (0 <= c_charge && c_charge <= MAX_COST)
|
|
|
|
|
|
{
|
2026-07-28 19:43:03 -06:00
|
|
|
|
ch->charge = static_cast<int>(c_charge);
|
2026-03-08 13:30:29 -06:00
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("Set."));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(
|
|
|
|
|
|
"That is not a reasonable cost."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case EDIT_CHANNEL_CPFLAGS:
|
|
|
|
|
|
{
|
|
|
|
|
|
int access = 0;
|
|
|
|
|
|
if (nullptr != s)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (strcmp(reinterpret_cast<const char *>(s), "join") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_JOIN;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (strcmp(reinterpret_cast<const char *>(s),
|
|
|
|
|
|
"receive") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_RECEIVE;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (strcmp(reinterpret_cast<const char *>(s),
|
|
|
|
|
|
"transmit") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_PLAYER_TRANSMIT;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (access)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (add_remove)
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->type |= access;
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@cpflags: Set."));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->type &= ~access;
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@cpflags: Cleared."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@cpflags: Unknown Flag."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case EDIT_CHANNEL_COFLAGS:
|
|
|
|
|
|
{
|
|
|
|
|
|
int access = 0;
|
|
|
|
|
|
if (nullptr != s)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (strcmp(reinterpret_cast<const char *>(s), "join") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_OBJECT_JOIN;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (strcmp(reinterpret_cast<const char *>(s),
|
|
|
|
|
|
"receive") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_OBJECT_RECEIVE;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (strcmp(reinterpret_cast<const char *>(s),
|
|
|
|
|
|
"transmit") == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
access = CHANNEL_OBJECT_TRANSMIT;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (access)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (add_remove)
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->type |= access;
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@coflags: Set."));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ch->type &= ~access;
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@coflags: Cleared."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@coflags: Unknown Flag."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sqlite_wt_channel(ch);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::CBoot(dbref executor, const UTF8 *pChannel,
|
|
|
|
|
|
const UTF8 *pVictim, int key)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = select_channel(pChannel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>("@cboot: Unknown channel."));
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *user = select_user(ch, executor);
|
|
|
|
|
|
if (nullptr == user)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(
|
|
|
|
|
|
"@cboot: You are not on that channel."));
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Permission check.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bControls = false;
|
|
|
|
|
|
m_pIPermissions->HasControl(executor, ch->charge_who, &bControls);
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
if (!bControls && !bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->Notify(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(
|
2026-07-26 21:52:48 -06:00
|
|
|
|
"@cboot: You can’t do that!"));
|
2026-03-08 13:30:29 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Resolve victim name to dbref.
|
|
|
|
|
|
//
|
|
|
|
|
|
dbref thing = NOTHING;
|
|
|
|
|
|
if (nullptr != pVictim)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->MatchThing(executor, pVictim, &thing);
|
|
|
|
|
|
}
|
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (NOTHING != thing)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(thing, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct comuser *vu = select_user(ch, thing);
|
|
|
|
|
|
if (nullptr == vu)
|
|
|
|
|
|
{
|
|
|
|
|
|
const UTF8 *pMoniker = nullptr;
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(thing, &pMoniker);
|
|
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("@cboot: %s is not on the channel."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
pMoniker ? reinterpret_cast<const char *>(pMoniker)
|
|
|
|
|
|
: "???");
|
|
|
|
|
|
m_pINotify->Notify(executor, msg);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Notify executor and victim.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *pExecMoniker = nullptr;
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(executor, &pExecMoniker);
|
|
|
|
|
|
const UTF8 *pVictMoniker = nullptr;
|
|
|
|
|
|
m_pIObjectInfo->GetMoniker(thing, &pVictMoniker);
|
|
|
|
|
|
|
|
|
|
|
|
UTF8 msg[MOD_LBUF_SIZE];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("You boot %s off channel %s."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
pVictMoniker ? reinterpret_cast<const char *>(pVictMoniker)
|
|
|
|
|
|
: "???",
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
m_pINotify->Notify(executor, msg);
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("%s boots you off channel %s."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
pExecMoniker ? reinterpret_cast<const char *>(pExecMoniker)
|
|
|
|
|
|
: "???",
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
m_pINotify->Notify(thing, msg);
|
|
|
|
|
|
|
|
|
|
|
|
if (!(key & CBOOT_QUIET))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Broadcast boot message to channel.
|
|
|
|
|
|
//
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:07:45 -06:00
|
|
|
|
M_("%s %s boots %s off the channel."),
|
2026-03-08 13:30:29 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->header),
|
|
|
|
|
|
pExecMoniker ? reinterpret_cast<const char *>(pExecMoniker)
|
|
|
|
|
|
: "???",
|
|
|
|
|
|
pVictMoniker ? reinterpret_cast<const char *>(pVictMoniker)
|
|
|
|
|
|
: "???");
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
SendChannelMessage(executor, ch, msg, nullptr, false);
|
2026-03-08 13:30:29 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Remove victim from channel.
|
|
|
|
|
|
//
|
|
|
|
|
|
do_delcomchannel(thing, ch->name, (key & CBOOT_QUIET) != 0);
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
MUX_RESULT CComsysMod::ProcessCommand(dbref executor, const UTF8 *pCmd,
|
|
|
|
|
|
bool *pbHandled)
|
|
|
|
|
|
{
|
|
|
|
|
|
*pbHandled = false;
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
|
|
|
|
|
// Parse "alias message" pattern.
|
|
|
|
|
|
//
|
|
|
|
|
|
const char *t = strchr(reinterpret_cast<const char *>(pCmd), ' ');
|
|
|
|
|
|
if ( nullptr == t
|
|
|
|
|
|
|| t - reinterpret_cast<const char *>(pCmd) > MAX_ALIAS_LEN
|
|
|
|
|
|
|| t[1] == '\0')
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Extract alias.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
UTF8 alias[MAX_ALIAS_LEN + 1];
|
2026-03-08 12:49:52 -06:00
|
|
|
|
size_t nAlias = t - reinterpret_cast<const char *>(pCmd);
|
|
|
|
|
|
memcpy(alias, pCmd, nAlias);
|
|
|
|
|
|
alias[nAlias] = '\0';
|
|
|
|
|
|
|
|
|
|
|
|
// Look up channel from alias.
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *ch = get_channel_from_alias(executor, alias);
|
|
|
|
|
|
if (ch[0] == '\0')
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:54:45 -06:00
|
|
|
|
// Found a valid alias — dispatch to the channel message processor.
|
2026-03-08 12:49:52 -06:00
|
|
|
|
//
|
2026-03-08 12:54:45 -06:00
|
|
|
|
UTF8 *pMsg = const_cast<UTF8 *>(reinterpret_cast<const UTF8 *>(t + 1));
|
|
|
|
|
|
do_processcom(executor, ch, pMsg);
|
|
|
|
|
|
*pbHandled = true;
|
2026-03-08 12:15:19 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 08:33:42 -06:00
|
|
|
|
MUX_RESULT CComsysMod::GetRevision(int *pRev)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pRev)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
*pRev = m_revision;
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Alias management.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::AddAlias(dbref executor, const UTF8 *pAlias,
|
|
|
|
|
|
const UTF8 *pChannel)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pAlias || '\0' == pAlias[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You need to specify a valid alias."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nullptr == pChannel || '\0' == pChannel[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You need to specify a channel."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate alias length.
|
|
|
|
|
|
//
|
|
|
|
|
|
size_t nAlias = strlen(reinterpret_cast<const char *>(pAlias));
|
|
|
|
|
|
if (nAlias > MAX_ALIAS_LEN)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You need to specify a valid alias."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(pChannel);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s does not exist yet."),
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(pChannel));
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!test_join_access(executor, ch))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Sorry, this channel type does not allow you to join."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = get_comsys(executor);
|
|
|
|
|
|
|
|
|
|
|
|
if (static_cast<int>(c.aliases.size()) >= MAX_ALIASES_PER_PLAYER)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Sorry, but you have reached the maximum number of "
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
"aliases allowed."));
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for duplicate alias.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (const auto &ca : c.aliases)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (ca.alias == reinterpret_cast<const char *>(pAlias))
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("That alias is already in use for channel %s."),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
ca.channel.c_str());
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Append and sort.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
com_alias ca;
|
|
|
|
|
|
ca.alias = reinterpret_cast<const char *>(pAlias);
|
|
|
|
|
|
ca.channel = reinterpret_cast<const char *>(ch->name);
|
|
|
|
|
|
c.aliases.push_back(std::move(ca));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
|
|
|
|
|
sort_com_aliases(c);
|
|
|
|
|
|
sqlite_wt_player_channel(executor, pAlias, ch->name);
|
|
|
|
|
|
|
|
|
|
|
|
// Join channel if not already a user.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr == select_user(ch, executor))
|
|
|
|
|
|
{
|
|
|
|
|
|
do_joinchannel(executor, ch);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s added with alias %s."),
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(ch->name),
|
|
|
|
|
|
reinterpret_cast<const char *>(pAlias));
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::DelAlias(dbref executor, const UTF8 *pAlias)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pAlias || '\0' == pAlias[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Need an alias to delete."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = get_comsys(executor);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (auto it = c.aliases.begin(); it != c.aliases.end(); ++it)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (it->alias != reinterpret_cast<const char *>(pAlias))
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Count other aliases for same channel.
|
|
|
|
|
|
//
|
|
|
|
|
|
int found = 0;
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
for (const auto &ca : c.aliases)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
if (ca.channel == it->channel)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
found++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If last alias for this channel, remove from channel.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (found <= 1)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
do_delcomchannel(executor,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(it->channel.c_str()),
|
|
|
|
|
|
false);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Alias %s for channel %s deleted."),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
reinterpret_cast<const char *>(pAlias),
|
|
|
|
|
|
it->channel.c_str());
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sqlite_wt_delete_player_channel(executor, pAlias);
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
c.aliases.erase(it);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
2026-07-28 12:12:21 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, M_("Unable to find that alias."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::ClearAliases(dbref executor)
|
|
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto it = m_comsys.find(executor);
|
|
|
|
|
|
if (it == m_comsys.end())
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
comsys_t &c = it->second;
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
// Walk backwards to safely remove each alias.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
while (!c.aliases.empty())
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
std::string alias = c.aliases.back().alias;
|
|
|
|
|
|
DelAlias(executor, reinterpret_cast<const UTF8 *>(alias.c_str()));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Channel creation/destruction.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::CreateChannel(dbref executor, const UTF8 *pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pName || '\0' == pName[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You must specify a channel to create."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check permission.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
if (!bCommAll)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You do not have permission to create channels."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-31 09:12:46 -06:00
|
|
|
|
// #1874: reject overlong names before any truncate/duplicate check.
|
|
|
|
|
|
// Truncating to MAX_CHANNEL_LEN after select_channel(full name) let
|
|
|
|
|
|
// "existing50byte" + "x" collide on the map key and replace the old
|
|
|
|
|
|
// channel without SQLite cleanup.
|
|
|
|
|
|
//
|
|
|
|
|
|
const size_t nName = strlen(reinterpret_cast<const char *>(pName));
|
|
|
|
|
|
if (nName > MAX_CHANNEL_LEN)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[128];
|
|
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
|
|
|
|
|
M_("Channel names must be at most %d characters."),
|
|
|
|
|
|
MAX_CHANNEL_LEN);
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for existing channel (full name — same length as stored key).
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != select_channel(pName))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("That channel already exists."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_FAIL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create the channel.
|
|
|
|
|
|
//
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
auto ch = std::make_unique<channel>();
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
|
|
|
|
|
strncpy(reinterpret_cast<char *>(ch->name),
|
|
|
|
|
|
reinterpret_cast<const char *>(pName), MAX_CHANNEL_LEN);
|
|
|
|
|
|
ch->name[MAX_CHANNEL_LEN] = '\0';
|
|
|
|
|
|
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(ch->header, MAX_HEADER_LEN + 1,
|
2026-07-27 22:40:00 -06:00
|
|
|
|
T("[%s]"), reinterpret_cast<const char *>(ch->name));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
|
|
|
|
|
ch->type = CHANNEL_DEFAULT;
|
|
|
|
|
|
ch->charge_who = executor;
|
|
|
|
|
|
ch->chan_obj = NOTHING;
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
channel *pCh = ch.get();
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
std::vector<UTF8> key(ch->name,
|
|
|
|
|
|
ch->name + strlen(reinterpret_cast<const char *>(ch->name)) + 1);
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
m_channels[key] = std::move(ch);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
sqlite_wt_channel(pCh);
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s created."),
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
reinterpret_cast<const char *>(pCh->name));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysMod::DestroyChannel(dbref executor, const UTF8 *pName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == pName || '\0' == pName[0])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You must specify a channel to destroy."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_INVALIDARG;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct channel *ch = select_channel(pName);
|
|
|
|
|
|
if (nullptr == ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("That channel does not exist."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_NOTFOUND;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check permission: must be owner or Comm_All.
|
|
|
|
|
|
//
|
|
|
|
|
|
bool bAllowed = (ch->charge_who == executor);
|
|
|
|
|
|
if (!bAllowed && nullptr != m_pIPermissions)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bCommAll = false;
|
|
|
|
|
|
m_pIPermissions->HasCommAll(executor, &bCommAll);
|
|
|
|
|
|
bAllowed = bCommAll;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!bAllowed)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pINotify->RawNotify(executor,
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("You do not have permission to destroy that channel."));
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
return MUX_E_PERMISSION;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 11:13:18 -06:00
|
|
|
|
// Capture canonical name before the channel object goes away.
|
|
|
|
|
|
//
|
|
|
|
|
|
const std::string channelName(
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
|
|
|
|
|
|
|
|
|
|
|
// Remove from SQLite. ON DELETE CASCADE clears channel_users and
|
|
|
|
|
|
// player_channels rows for this channel name.
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
//
|
2026-03-10 10:39:15 -06:00
|
|
|
|
if (nullptr != m_pIStorage)
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
{
|
fix(comsys/mail): check the storage writes both modules were discarding (#1630)
Fifteen storage write results were discarded across the two modules. Each
is a write that can be refused -- foreign keys, constraint violations, a
closed database, a full disk -- and each failed silently, letting in-memory
state and stored state diverge with nothing to notice. That is the shape of
#1564, #1585, #1620 and #1587.
#1629 fixed one of them (DeleteMailBody, refused by a foreign key) and cost
a full investigation to find, because the only symptom was mail being
unreadable in-session while a restart read it fine.
mail_mod.cpp 7 UpdateMailReadFlags, DeleteMailHeader,
DeleteAllMailHeaders, SyncMailBody, PutMeta,
ClearMailAliases, SyncMailAlias
comsys_mod.cpp 8 SyncChannelUser, SyncChannel, SyncPlayerChannel,
DeletePlayerChannel, DeleteChannelUser,
DeleteChannel x2, DeleteAllPlayerChannels
Also InsertMailHeader, which is not in #1630's list: its result was already
consumed for the rowid, so a failure set sqlite_id = -1 and said nothing.
The message then lives in memory and not in the database -- it survives the
session and vanishes on restart. Same class, so it is logged too.
Repeating #1629's fourteen-line block fifteen times would be worse than the
bug, so each module gets a log_storage_failure(mr, fmt, ...) helper that is
silent on success. A call site wraps its call directly and names the
operation, the key identifying the row, and the result code -- #1587 needed
to know *which* body could not be deleted. The helper carries
__attribute__((format(printf, 3, 4))), so GCC type-checked all fifteen
format strings against their arguments.
No behaviour change beyond the logging.
## Verified, not assumed
The logging only fires on failure, so a green run proves nothing by itself.
Forced a genuine refusal using channel_users.channel_name REFERENCES
channels(name):
control channel_users = ichan|1
injected channel_users empty -- the write was refused
CComsysStorage::SyncChannelUser returns MUX_E_FAIL
log_storage_failure fires; start_log reports fStarted = 1
Injection reverted; no residue.
## One caveat, filed as #1633
#1630 says checking "converts silent divergence into a log grep". Under
muxscript that grep can never match. CLogFile starts with bEnabled = false,
WriteBuffer early-returns on it, and the only StartLogging() caller in the
tree is mux/src/driver.cpp -- netmux. start_log still reports fStarted = 1,
so a caller has every reason to believe it logged.
So these lines land in a real server and are invisible to the harness that
tests comsys/mail. The already-merged #1620 and #1629 lines have the same
property. Filed separately rather than fixed here.
(The startup line "Comsys: using module implementation." is a bare
fprintf(stderr, ...) at engine_com.cpp:2944, not evidence that logging
works -- I nearly took it as such.)
tests/comsys_handoff: 13 passed, 0 failed.
Refs #1587, #1614, #1620, #1629, #1633.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:19 -06:00
|
|
|
|
log_storage_failure(m_pIStorage->DeleteChannel(ch->name),
|
|
|
|
|
|
"DeleteChannel(channel=%s)",
|
|
|
|
|
|
reinterpret_cast<const char *>(ch->name));
|
2026-07-25 08:33:42 -06:00
|
|
|
|
bump_revision();
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 11:13:18 -06:00
|
|
|
|
// #1199: purge other players' in-memory aliases that pointed here.
|
|
|
|
|
|
// (SQLite rows are already CASCADE-deleted; still drop local aliases.)
|
|
|
|
|
|
//
|
|
|
|
|
|
for (auto &kv : m_comsys)
|
|
|
|
|
|
{
|
|
|
|
|
|
comsys_t &c = kv.second;
|
|
|
|
|
|
for (auto ait = c.aliases.begin(); ait != c.aliases.end(); )
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ait->channel == channelName)
|
|
|
|
|
|
{
|
|
|
|
|
|
sqlite_wt_delete_player_channel(c.who,
|
|
|
|
|
|
reinterpret_cast<const UTF8 *>(ait->alias.c_str()));
|
|
|
|
|
|
ait = c.aliases.erase(ait);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
++ait;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
// Notify before erasing (ch will be destroyed by unique_ptr).
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
//
|
|
|
|
|
|
if (nullptr != m_pINotify)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 msg[256];
|
fix(comsys): the module's command surface, compared against the engine (#1640)
Four divergences from the issue, plus three more the probe found once the
fixtures stopped being plain ASCII.
From #1640:
* @clist/full ignored the switch and printed the default listing, losing
Header, Access, Users and Msgs -- the four columns the switch exists for.
* @cwho printed a bare name where the engine prints unparse_object(), so
staff lost the dbref and flags that identify WHICH object is on a channel.
* comtitles reached no message at all. The module stored them and
round-tripped them through SyncChannelUser, then never read them back when
composing one: not speech, not poses, not join/leave, and not spoof
channels, where the comtitle is supposed to REPLACE the speaker's name.
* delcom emitted the channel broadcast instead of the leaver's own
confirmation -- which the leaver cannot even see, since clearing
bConnected first is what suppresses it, so delcom looked like it had done
nothing and any trigger matching "^You have left channel" stopped firing.
Comtitles are one fix, not four: BuildSpeakerPrefix mirrors the engine's
BuildChannelMessage, including that the LISTENER decides whether a comtitle
is shown, so SendChannelMessage now takes both variants and picks per
recipient.
Found while verifying, none of which an ASCII fixture can see:
* The ENGINE conflated byte offset with column offset in @clist/full
(comsys.cpp:2877), resetting iPos.m_column to the byte offset after
writing JXR. Those are equal only while every preceding field is plain
ASCII; a colored channel header makes the byte count larger, PadField
believes it is already past column 56, and the Users column shifts left.
* The module stored channel headers with ANSI uncollapsed -- a raw strncpy
where do_cheader runs StripTabsAndTruncate -- so `[ansi(r,RED)][ansi(b,BLU)]`
persisted an extra reset between the codes. A divergence in SQLite, not
on screen, so it survived a handoff. The same strncpy truncated at
MAX_HEADER_LEN *bytes*, which can split a codepoint or a color code.
* @clist/headers was missing the engine's pad to column 79.
Column layout in the module now goes through StripTabsAndTruncate/PadField
rather than printf field widths. A printf precision counts codepoints and
knows nothing about PUA color; StripTabsAndTruncate carries a byte limit and
a column limit as a pair and reserves budget for the closing color sequence.
My first version of this fix hand-rolled the column stops with %-14.13s and
verified byte-identical output against the engine -- for ASCII only, which is
exactly the blind spot that produced two of the three findings above. #1649
covers the general problem; nothing here waits on it.
@cwho deliberately reproduces the engine's composition including
strip_color(): @cwho discards color so its width arithmetic is honest while
@clist two functions away preserves it. Both are coping strategies for the
primitive #1649 proposes, and this file's job is parity, not unilaterally
improving one side into a fresh divergence.
mux_IObjectInfo gains UnparseObject: the visibility rule is Examinable() plus
the CHOWN_OK/JUMP_OK/LINK_OK/DESTROY_OK/ABODE exceptions, which a module
cannot compute from GetFlags/DecodeFlags. Caller-supplied buffer, matching
AtrGet, so nothing crosses the DLL boundary needing to be freed on the far
side. CID_ObjectInfo is UseSameProcess only, so there is no proxy/stub to
update.
New harness, tests/comsys_cmdparity: one command stream, two fresh databases,
output diffed. The two existing comsys harnesses compare state handoff and
delivery hooks; neither compares plain command output, and both scored green
against all seven of these. Fixtures are deliberately colored and CJK.
Verified it goes red: reinstating the engine's PadField conflation fails case
3 while cases 1 and 2 still pass, which is the ASCII blind spot reproduced on
demand.
Windows Server 2022, MSVC 14.51, Release x64: three parity streams identical;
comsys_handoff 13/13; comsys_mogrify 5/5; smoke 1555 succeeded / 5 failed /
0 crashes, unchanged, the five being the pre-existing no-OpenSSL gap (#1641).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:31:58 -06:00
|
|
|
|
mux_sprintf(msg, sizeof(msg),
|
2026-07-28 12:12:21 -06:00
|
|
|
|
M_("Channel %s destroyed."),
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
reinterpret_cast<const char *>(pName));
|
|
|
|
|
|
m_pINotify->RawNotify(executor, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Comsys module: full STL conversion — eliminate manual memory management
Replace all hand-managed C-era data structures with STL containers:
- comuser: strdup/free title → std::string, on_next linked list → bConnected flag
- channel: realloc'd sorted comuser** array → std::map<dbref, comuser>,
on_users intrusive linked list eliminated (iterate map filtering bConnected)
- comsys_t: packed ALIAS_SIZE-stride array + parallel strdup'd channel array →
std::vector<com_alias> with std::string members, hash chain pointer removed
- m_channels: raw channel* → std::unique_ptr<channel>
- m_comsys_table[500] hash table → std::unordered_map<dbref, comsys_t>
- Removed NUM_COMSYS, ALIAS_SIZE, m_num_channels, create_new_comsys, add_comsys
- sort_com_aliases → std::sort, select_user → map::find, get_comsys returns ref
Net result: −641 lines deleted, +264 added. All 551 smoke tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:03:17 -06:00
|
|
|
|
// Remove from map — unique_ptr cleans up channel and all users.
|
|
|
|
|
|
//
|
|
|
|
|
|
std::vector<UTF8> key(ch->name,
|
|
|
|
|
|
ch->name + strlen(reinterpret_cast<const char *>(ch->name)) + 1);
|
|
|
|
|
|
m_channels.erase(key);
|
|
|
|
|
|
|
Route addcom/delcom/clearcom/@ccreate/@cdestroy through comsys module
When the comsys module is loaded, server-side command handlers for
addcom, delcom, clearcom, @ccreate, and @cdestroy delegate to the
module's AddAlias/DelAlias/ClearAliases/CreateChannel/DestroyChannel
methods. do_comconnect/do_comdisconnect/do_channelnuke return early
when the module is loaded since it handles those via IServerEventsSink.
The module now implements:
- AddAlias: validates alias, checks join access, grows arrays, sorts,
writes through to SQLite, auto-joins channel
- DelAlias: finds alias, optionally removes from channel if last alias,
cleans up SQLite
- ClearAliases: removes all aliases for a player
- CreateChannel: permission check, creates channel struct, writes to
SQLite, adds to module's channel map
- DestroyChannel: permission check, frees users, removes from SQLite
and channel map
Supporting helpers: test_join_access, do_delcomchannel, sort_com_aliases,
sqlite_wt_player_channel, sqlite_wt_delete_player_channel,
sqlite_wt_delete_channel_user.
This resolves the dual-data problem: all runtime channel mutations now
go through the module when it is loaded, keeping the module's in-memory
state and SQLite in sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:00:40 -06:00
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// mux_IServerEventsSink implementation — server lifecycle events.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::startup(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
MUX_RESULT mr = m_pILog->start_log(&fStarted, LOG_ALWAYS,
|
|
|
|
|
|
T("INI"), T("INFO"));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module startup complete."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::presync_database(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Nothing needed — SQLite write-through handles persistence.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::presync_database_sigsegv(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::dump_database(int dump_type)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(dump_type);
|
|
|
|
|
|
// Nothing needed — SQLite WAL checkpoint handles durability.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::dump_complete_signal(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::shutdown(void)
|
|
|
|
|
|
{
|
2026-03-10 10:50:00 -06:00
|
|
|
|
if (nullptr != m_pIStorage)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIStorage->Release();
|
|
|
|
|
|
m_pIStorage = nullptr;
|
|
|
|
|
|
}
|
2026-03-08 12:49:52 -06:00
|
|
|
|
|
2026-03-08 12:15:19 -06:00
|
|
|
|
if (nullptr != m_pILog)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool fStarted;
|
|
|
|
|
|
MUX_RESULT mr = m_pILog->start_log(&fStarted, LOG_ALWAYS,
|
|
|
|
|
|
T("INI"), T("INFO"));
|
|
|
|
|
|
if (MUX_SUCCEEDED(mr) && fStarted)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pILog->log_text(T("Comsys module shutting down."));
|
|
|
|
|
|
m_pILog->end_log();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::dbck(void)
|
|
|
|
|
|
{
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// Channel consistency checks (#1195):
|
|
|
|
|
|
// 1. Prune users with invalid dbrefs only — object listeners stay.
|
2026-03-27 16:45:39 -06:00
|
|
|
|
// 2. Reassign channel ownership if owner is gone.
|
2026-07-25 13:43:00 -06:00
|
|
|
|
// 3. Prune comsys alias rows for destroyed players.
|
2026-03-27 16:45:39 -06:00
|
|
|
|
//
|
|
|
|
|
|
for (auto &kv : m_channels)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct channel *ch = kv.second.get();
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = ch->users.begin(); it != ch->users.end(); )
|
|
|
|
|
|
{
|
2026-07-25 13:43:00 -06:00
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsValid(it->second.who, &bValid);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bValid)
|
2026-03-27 16:45:39 -06:00
|
|
|
|
{
|
|
|
|
|
|
it = ch->users.erase(it);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-25 13:43:00 -06:00
|
|
|
|
if (ch->charge_who != NOTHING && nullptr != m_pIObjectInfo)
|
2026-03-27 16:45:39 -06:00
|
|
|
|
{
|
2026-07-25 13:43:00 -06:00
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
m_pIObjectInfo->IsValid(ch->charge_who, &bValid);
|
|
|
|
|
|
if (!bValid)
|
2026-03-27 16:45:39 -06:00
|
|
|
|
{
|
|
|
|
|
|
ch->charge_who = 1; // GOD
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = m_comsys.begin(); it != m_comsys.end(); )
|
|
|
|
|
|
{
|
|
|
|
|
|
bool bPlayer = false;
|
2026-07-25 13:43:00 -06:00
|
|
|
|
if (nullptr != m_pIObjectInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pIObjectInfo->IsPlayer(it->first, &bPlayer);
|
|
|
|
|
|
}
|
2026-03-27 16:45:39 -06:00
|
|
|
|
if (!bPlayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
it = m_comsys.erase(it);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-08 12:15:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::connect(dbref player, int isnew, int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(isnew);
|
|
|
|
|
|
UNUSED_PARAMETER(num);
|
|
|
|
|
|
PlayerConnect(player);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::disconnect(dbref player, int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(num);
|
|
|
|
|
|
PlayerDisconnect(player);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::data_create(dbref object)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::data_clone(dbref clone, dbref source)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(clone);
|
|
|
|
|
|
UNUSED_PARAMETER(source);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CComsysMod::data_free(dbref object)
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayerNuke(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// CComsysModFactory — boilerplate.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CComsysModFactory::CComsysModFactory(void) : m_cRef(1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CComsysModFactory::~CComsysModFactory()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysModFactory::QueryInterface(MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mux_IID_IUnknown == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mux_IID_IClassFactory == iid)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = static_cast<mux_IClassFactory *>(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
*ppv = nullptr;
|
|
|
|
|
|
return MUX_E_NOINTERFACE;
|
|
|
|
|
|
}
|
|
|
|
|
|
reinterpret_cast<mux_IUnknown *>(*ppv)->AddRef();
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysModFactory::AddRef(void)
|
|
|
|
|
|
{
|
2026-04-05 15:03:38 -06:00
|
|
|
|
return m_cRef.fetch_add(1, std::memory_order_relaxed) + 1;
|
2026-03-08 12:15:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t CComsysModFactory::Release(void)
|
|
|
|
|
|
{
|
2026-04-05 15:03:38 -06:00
|
|
|
|
uint32_t prev = m_cRef.fetch_sub(1, std::memory_order_acq_rel);
|
|
|
|
|
|
if (1 == prev)
|
2026-03-08 12:15:19 -06:00
|
|
|
|
{
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-04-05 15:03:38 -06:00
|
|
|
|
return prev - 1;
|
2026-03-08 12:15:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysModFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
|
|
|
|
|
|
MUX_IID iid, void **ppv)
|
|
|
|
|
|
{
|
|
|
|
|
|
UNUSED_PARAMETER(pUnknownOuter);
|
|
|
|
|
|
|
|
|
|
|
|
CComsysMod *pComsysMod = nullptr;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
pComsysMod = new CComsysMod;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (...)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == pComsysMod)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MUX_E_OUTOFMEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT mr = pComsysMod->FinalConstruct();
|
|
|
|
|
|
if (MUX_FAILED(mr))
|
|
|
|
|
|
{
|
|
|
|
|
|
pComsysMod->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mr = pComsysMod->QueryInterface(iid, ppv);
|
|
|
|
|
|
pComsysMod->Release();
|
|
|
|
|
|
return mr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MUX_RESULT CComsysModFactory::LockServer(bool bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
g_cServerLocks++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
g_cServerLocks--;
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUX_S_OK;
|
|
|
|
|
|
}
|