Skip to content

Conversation

@Halvance
Copy link

@Halvance Halvance commented Dec 17, 2025

GMCP Improvements:

  • Enhanced GMCP message handling in proxy layer
  • Added raw game text signal (sig_rawGameText) for fallback parsing
  • Improved GMCP module support detection
  • Better error handling for GMCP communication

Clock Broadcasting:

  • Convert gmcpBroadcast and gmcpBroadcastInterval to NamedConfig
  • Add change callbacks for immediate broadcaster restart
  • Fix checkbox/interval controls to work without reconnecting
  • Configurable broadcast interval (default: 2.5 seconds)
  • Auto-start broadcaster when client supports MUME.Time module

GameObserver Integration:

  • Connect raw game text to GameObserver for fallback parsing
  • Enable communication parsing when GMCP unavailable
  • Support for yell fallback parsing configuration

Documentation:

  • Added mudlet_clock_example.lua showing GMCP integration
  • Example demonstrates clock synchronization in Mudlet

This PR establishes the foundation for real-time GMCP features and enables better synchronization between MMapper and clients.

Summary by Sourcery

Enhance GMCP-based time synchronization and introduce richer UI/configuration options, including hotkey and communications settings.

New Features:

  • Add GMCP-based MUME.Time clock broadcaster with configurable interval that auto-starts when the client enables the MUME.Time module.
  • Expose GMCP clock broadcast enable/interval settings in the MUME protocol preferences, applying changes immediately without reconnecting.
  • Provide a Mudlet example script demonstrating how to consume MUME.Time.Info GMCP messages from MMapper for clock and day/night integration.
  • Introduce configurable canvas texture sets, seasonal textures, layer transparency, background images, and per-marker visibility controls.
  • Add centralized hotkey configuration for common file, edit, view, panel, mouse mode, and room operations.
  • Add configurable communications panel settings including per-channel colors, styling, timestamps, logging, and tab muting.

Enhancements:

  • Emit raw game text from the XML parser and route it through GameObserver to support fallback parsing when GMCP is unavailable.
  • Advertise supported GMCP modules to the client and react to supported module enablement/disablement, including MUME.Time.
  • Extend the MumeClock to emit season-change signals and to immediately update map lighting based on GMCP vitals weather flags.
  • Adjust default background color and extend canvas advanced options like horizontal angle range for improved visualization.

GMCP Improvements:
- Enhanced GMCP message handling in proxy layer
- Added raw game text signal (sig_rawGameText) for fallback parsing
- Improved GMCP module support detection
- Better error handling for GMCP communication

Clock Broadcasting:
- Convert gmcpBroadcast and gmcpBroadcastInterval to NamedConfig
- Add change callbacks for immediate broadcaster restart
- Fix checkbox/interval controls to work without reconnecting
- Configurable broadcast interval (default: 2.5 seconds)
- Auto-start broadcaster when client supports MUME.Time module

GameObserver Integration:
- Connect raw game text to GameObserver for fallback parsing
- Enable communication parsing when GMCP unavailable
- Support for yell fallback parsing configuration

Documentation:
- Added mudlet_clock_example.lua showing GMCP integration
- Example demonstrates clock synchronization in Mudlet

This PR establishes the foundation for real-time GMCP features
and enables better synchronization between MMapper and clients.
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 17, 2025

Reviewer's Guide

Adds a GMCP-aware clock broadcaster over the proxy (driven by new MUME.Time GMCP support), wires raw game text into GameObserver for fallback parsing, and extends configuration/UI (including new NamedConfigs, hotkey/comms/canvas options) plus Mudlet example script to support real‑time time/season info and communication features.

Sequence diagram for GMCP MUME.Time clock broadcasting

sequenceDiagram
    actor MudClient
    participant UserTelnet
    participant Proxy
    participant MumeClock
    participant MumeServer

    MudClient->>UserTelnet: send GMCP Core.Hello
    UserTelnet->>UserTelnet: virt_receiveGmcpMessage(Core.Hello)
    UserTelnet->>UserTelnet: sendSupportedGmcpModules()
    UserTelnet-->>MudClient: GMCP Core.Supports.Set (MUME.Time 1, ...)

    MudClient->>UserTelnet: enable GMCP module MUME.Time
    UserTelnet->>UserTelnet: receiveGmcpModule(MUME.Time, true)
    UserTelnet->>UserTelnet: m_gmcp.modules.insert(mod)
    UserTelnet->>UserTelnet: m_gmcp.supported[type] = version
    UserTelnet->>UserTelnetOutputs: onGmcpModuleEnabled(MUME_TIME, true)

    UserTelnetOutputs->>Proxy: virt_onGmcpModuleEnabled(MUME_TIME, true)
    Proxy->>Proxy: startClockBroadcaster()
    Proxy->>Proxy: check config.mumeClock.gmcpBroadcast
    Proxy->>Proxy: isUserGmcpModuleEnabled(MUME_TIME)
    Proxy->>Proxy: create QTimer m_clockBroadcastTimer
    Proxy->>Proxy: m_clockBroadcastTimer.start(interval)
    Proxy->>Proxy: broadcastClockInfo() (initial)

    loop every gmcpBroadcastInterval
        Proxy->>Proxy: broadcastClockInfo()
        Proxy->>MumeClock: getMumeMoment()
        Proxy->>MumeClock: getPrecision()
        MumeClock-->>Proxy: MumeMoment, precision
        Proxy->>Proxy: build QJsonObject with time/season/moon
        Proxy->>Proxy: create GmcpMessage(MUME_TIME_INFO, json)
        Proxy->>UserTelnet: gmcpToUser(MUME.Time.Info)
        UserTelnet-->>MudClient: send GMCP MUME.Time.Info
        MudClient->>MudClient: handle gmcp.MUME.Time.Info (Lua script)
    end

    Note over Proxy,MudClient: Broadcaster stops when MUME.Time disabled or Proxy destroyed
Loading

Sequence diagram for raw game text fallback parsing via GameObserver

sequenceDiagram
    participant MumeServer
    participant Proxy
    participant MumeXmlParser
    participant GameObserver
    participant FallbackParser

    MumeServer-->>Proxy: raw Telnet data
    Proxy->>MumeXmlParser: parse(TelnetData, isGoAhead)
    MumeXmlParser->>MumeXmlParser: accumulate m_lineToUser

    alt full line ready
        MumeXmlParser-->>Proxy: sendToUser(FromMud, m_lineToUser)
        MumeXmlParser-->>MumeXmlParser: emit sig_rawGameText(m_lineToUser)
        Proxy->>Proxy: connected lambda for sig_rawGameText
        Proxy->>GameObserver: observeRawGameText(text)
        GameObserver->>GameObserver: sig2_rawGameText.invoke(text)
        GameObserver-->>FallbackParser: raw game text callback
        FallbackParser->>FallbackParser: parse yells and comms when GMCP unavailable
    end
Loading

Sequence diagram for GMCP config change callbacks restarting broadcaster

sequenceDiagram
    participant User
    participant MumeProtocolPage
    participant Configuration_mumeClock as MumeClockSettings
    participant Proxy

    User->>MumeProtocolPage: toggle gmcpBroadcast checkbox
    MumeProtocolPage->>Configuration_mumeClock: gmcpBroadcast.set(enabled)

    Note over Configuration_mumeClock,Proxy: NamedConfig change callback registered in Proxy::init

    Configuration_mumeClock-->>Proxy: gmcpBroadcast change callback
    Proxy->>Proxy: stopClockBroadcaster()
    Proxy->>Proxy: startClockBroadcaster()

    User->>MumeProtocolPage: change gmcpBroadcastInterval spinbox
    MumeProtocolPage->>Configuration_mumeClock: gmcpBroadcastInterval.set(value)
    Configuration_mumeClock-->>Proxy: gmcpBroadcastInterval change callback
    Proxy->>Proxy: stopClockBroadcaster()
    Proxy->>Proxy: startClockBroadcaster()
Loading

Updated class diagram for configuration, proxy, GMCP, clock, and observer

classDiagram
    class Configuration {
        +General general
        +ConnectionSettings connection
        +CanvasSettings canvas
        +Hotkeys hotkeys
        +CommsSettings comms
        +MumeClockSettings mumeClock
    }

    class Configuration_General {
        +bool enableYellFallbackParsing
    }

    class CanvasSettings {
        +TextureSetEnum textureSet
        +bool enableSeasonalTextures
        +float layerTransparency
        +bool enableRadialTransparency
        +Advanced advanced
        +VisibilityFilter visibilityFilter
        +void read(QSettings conf)
        +void write(QSettings conf) const
    }

    class Advanced {
        +bool useBackgroundImage
        +QString backgroundImagePath
        +int backgroundFitMode
        +float backgroundOpacity
        +float backgroundFocusedScale
        +float backgroundFocusedOffsetX
        +float backgroundFocusedOffsetY
        +FixedPoint fov
        +FixedPoint verticalAngle
        +FixedPoint horizontalAngle
        +FixedPoint layerHeight
        +void registerChangeCallback(ChangeMonitor_Lifetime lifetime, ChangeMonitor_Function callback)
    }

    class VisibilityFilter {
        +NamedConfig~bool~ generic
        +NamedConfig~bool~ herb
        +NamedConfig~bool~ river
        +NamedConfig~bool~ place
        +NamedConfig~bool~ mob
        +NamedConfig~bool~ comment
        +NamedConfig~bool~ road
        +NamedConfig~bool~ object
        +NamedConfig~bool~ action
        +NamedConfig~bool~ locality
        +NamedConfig~bool~ connections
        +bool isVisible(InfomarkClassEnum markerClass) const
        +void setVisible(InfomarkClassEnum markerClass, bool visible)
        +bool isConnectionsVisible() const
        +void setConnectionsVisible(bool visible)
        +void showAll()
        +void hideAll()
        +void registerChangeCallback(ChangeMonitor_Lifetime lifetime, ChangeMonitor_Function callback)
    }

    class Hotkeys {
        +NamedConfig~QString~ fileOpen
        +NamedConfig~QString~ fileSave
        +NamedConfig~QString~ fileReload
        +NamedConfig~QString~ fileQuit
        +NamedConfig~QString~ editUndo
        +NamedConfig~QString~ editRedo
        +NamedConfig~QString~ editPreferences
        +NamedConfig~QString~ editPreferencesAlt
        +NamedConfig~QString~ editFindRooms
        +NamedConfig~QString~ editRoom
        +NamedConfig~QString~ viewZoomIn
        +NamedConfig~QString~ viewZoomOut
        +NamedConfig~QString~ viewZoomReset
        +NamedConfig~QString~ viewLayerUp
        +NamedConfig~QString~ viewLayerDown
        +NamedConfig~QString~ viewLayerReset
        +NamedConfig~QString~ viewRadialTransparency
        +NamedConfig~QString~ viewStatusBar
        +NamedConfig~QString~ viewScrollBars
        +NamedConfig~QString~ viewMenuBar
        +NamedConfig~QString~ viewAlwaysOnTop
        +NamedConfig~QString~ panelLog
        +NamedConfig~QString~ panelClient
        +NamedConfig~QString~ panelGroup
        +NamedConfig~QString~ panelRoom
        +NamedConfig~QString~ panelAdventure
        +NamedConfig~QString~ panelComms
        +NamedConfig~QString~ panelDescription
        +NamedConfig~QString~ modeMoveMap
        +NamedConfig~QString~ modeRaypick
        +NamedConfig~QString~ modeSelectRooms
        +NamedConfig~QString~ modeSelectMarkers
        +NamedConfig~QString~ modeSelectConnection
        +NamedConfig~QString~ modeCreateMarker
        +NamedConfig~QString~ modeCreateRoom
        +NamedConfig~QString~ modeCreateConnection
        +NamedConfig~QString~ modeCreateOnewayConnection
        +NamedConfig~QString~ roomCreate
        +NamedConfig~QString~ roomMoveUp
        +NamedConfig~QString~ roomMoveDown
        +NamedConfig~QString~ roomMergeUp
        +NamedConfig~QString~ roomMergeDown
        +NamedConfig~QString~ roomDelete
        +NamedConfig~QString~ roomConnectNeighbors
        +NamedConfig~QString~ roomMoveToSelected
        +NamedConfig~QString~ roomUpdateSelected
        +void read(QSettings conf)
        +void write(QSettings conf) const
    }

    class CommsSettings {
        +NamedConfig~QColor~ tellColor
        +NamedConfig~QColor~ whisperColor
        +NamedConfig~QColor~ groupColor
        +NamedConfig~QColor~ askColor
        +NamedConfig~QColor~ sayColor
        +NamedConfig~QColor~ emoteColor
        +NamedConfig~QColor~ socialColor
        +NamedConfig~QColor~ yellColor
        +NamedConfig~QColor~ narrateColor
        +NamedConfig~QColor~ prayColor
        +NamedConfig~QColor~ shoutColor
        +NamedConfig~QColor~ singColor
        +NamedConfig~QColor~ backgroundColor
        +NamedConfig~QColor~ talkerYouColor
        +NamedConfig~QColor~ talkerPlayerColor
        +NamedConfig~QColor~ talkerNpcColor
        +NamedConfig~QColor~ talkerAllyColor
        +NamedConfig~QColor~ talkerNeutralColor
        +NamedConfig~QColor~ talkerEnemyColor
        +NamedConfig~bool~ yellAllCaps
        +NamedConfig~bool~ whisperItalic
        +NamedConfig~bool~ emoteItalic
        +NamedConfig~bool~ showTimestamps
        +NamedConfig~bool~ saveLogOnExit
        +NamedConfig~QString~ logDirectory
        +NamedConfig~bool~ muteDirectTab
        +NamedConfig~bool~ muteLocalTab
        +NamedConfig~bool~ muteGlobalTab
        +void read(QSettings conf)
        +void write(QSettings conf) const
    }

    class MumeClockSettings {
        +int64_t startEpoch
        +bool display
        +NamedConfig~bool~ gmcpBroadcast
        +NamedConfig~int~ gmcpBroadcastInterval
        +MumeClockSettings()
        +void read(QSettings conf)
        +void write(QSettings conf) const
    }

    class Proxy {
        -QTimer* m_clockBroadcastTimer
        +void init()
        +void gmcpToUser(GmcpMessage msg)
        +void startClockBroadcaster()
        +void stopClockBroadcaster()
        +void broadcastClockInfo()
        +~Proxy()
    }

    class UserTelnetOutputs {
        +void onAnalyzeUserStream(RawBytes bytes, bool flush)
        +void onRelayGmcpFromUserToMud(GmcpMessage msg)
        +void onRelayNawsFromUserToMud(int width, int height)
        +void onRelayTermTypeFromUserToMud(TelnetTermTypeBytes bytes)
        +void onGmcpModuleEnabled(GmcpModuleTypeEnum type, bool enabled)
        ..virtual..
        +virt_onAnalyzeUserStream(RawBytes bytes, bool flush)
        +virt_onRelayGmcpFromUserToMud(GmcpMessage msg)
        +virt_onRelayNawsFromUserToMud(int width, int height)
        +virt_onRelayTermTypeFromUserToMud(TelnetTermTypeBytes bytes)
        +virt_onGmcpModuleEnabled(GmcpModuleTypeEnum type, bool enabled)
    }

    class UserTelnet {
        +void virt_receiveGmcpMessage(GmcpMessage msg)
        +bool virt_isGmcpModuleEnabled(GmcpModuleTypeEnum name) const
        -void receiveGmcpModule(GmcpModule mod, bool enabled)
        -void resetGmcpModules()
        -void sendSupportedGmcpModules()
    }

    class MumeClock {
        -int64_t m_mumeStartEpoch
        -MumeSeasonEnum m_lastSeasonEmitted
        +MumeMoment getMumeMoment() const
        +MumeClockPrecisionEnum getPrecision() const
        +int64_t getLastSyncEpoch() const
        +void parseMumeTime(QString mumeTime)
        +void parseMumeTime(QString mumeTime, int64_t secsSinceEpoch)
        +void parseWeather(MumeTimeEnum time, int64_t secsSinceEpoch)
        +void parseClockTime(QString clockTime)
        +void parseClockTime(QString clockTime, int64_t secsSinceEpoch)
        +void parseMSSP(MsspTime msspTime)
        -void onUserGmcp(GmcpMessage msg)
        +signal sig_log(QString msg, QString category)
        +signal sig_seasonChanged(MumeSeasonEnum newSeason)
    }

    class GameObserver {
        +Signal2~GmcpMessage~ sig2_sentToUserGmcp
        +Signal2~bool~ sig2_toggledEchoMode
        +Signal2~QString~ sig2_rawGameText
        +void observeConnected()
        +void observeDisconnected()
        +void observeSentToUser(QString text)
        +void observeSentToUserGmcp(GmcpMessage msg)
        +void observeToggledEchoMode(bool echo)
        +void observeRawGameText(QString text)
    }

    class MumeXmlParser {
        +signal sig_rawGameText(QString text)
        +void parse(TelnetData data, bool isGoAhead)
        +void parseGmcpCharVitals(JsonObj obj)
    }

    class GmcpMessage {
        +GmcpMessageTypeEnum type
        +GmcpJson payload
        +bool isCoreHello() const
        +ConstString getName() const
    }

    class GmcpModule {
        +GmcpModuleTypeEnum getType() const
        +bool isSupported() const
        +bool hasVersion() const
        +int getVersion() const
        +std::string getNormalizedName() const
    }

    class MumeProtocolPage {
        +void slot_loadConfig()
        +void slot_internalEditorRadioButtonChanged(bool enabled)
        +void slot_externalEditorCommandTextChanged(QString text)
        +void slot_externalEditorBrowseButtonClicked(bool checked)
        +void slot_gmcpBroadcastCheckBoxChanged(int state)
        +void slot_gmcpIntervalSpinBoxChanged(int value)
    }

    Configuration o-- CanvasSettings
    Configuration o-- Hotkeys
    Configuration o-- CommsSettings
    Configuration o-- MumeClockSettings
    Configuration o-- Configuration_General

    CanvasSettings o-- Advanced
    CanvasSettings o-- VisibilityFilter

    Proxy ..> MumeClock : uses
    Proxy ..> GameObserver : uses
    Proxy ..> MumeXmlParser : uses
    Proxy ..> UserTelnet : interacts

    UserTelnetOutputs <|.. Proxy : implements virt_onGmcpModuleEnabled
    UserTelnet ..> UserTelnetOutputs : uses callbacks
    UserTelnet ..> GmcpModule : receiveGmcpModule
    UserTelnet ..> GmcpMessage : sendSupportedGmcpModules

    MumeXmlParser ..> GameObserver : emits sig_rawGameText via Proxy

    MumeClockSettings ..> MumeClock : configures
    MumeProtocolPage ..> Configuration : reads and writes mumeClock
    MumeProtocolPage ..> Proxy : indirectly restarts broadcaster via change callbacks
Loading

File-Level Changes

Change Details Files
GMCP clock broadcasting over new MUME.Time GMCP module
  • Add MUME.Time to GMCP module registry and MUME.Time.Info to GMCP message types, updating enum counts
  • Have UserTelnet respond to Core.Hello with Core.Supports.Set advertising MMapper-supported modules and surface module enable/disable events through UserTelnetOutputs
  • Teach Proxy to react to GMCP module enable/disable (for MUME.Time) and manage a QTimer-based broadcaster lifecycle
  • Implement Proxy::broadcastClockInfo to serialize current MUME time/season/moon/dawn-dusk metadata to compact JSON and send as MUME.Time.Info GMCP messages at a configurable interval
  • Hook GMCP clock broadcasting into MumeProtocol preferences and configuration via new NamedConfig fields for enable flag and interval
src/proxy/proxy.cpp
src/proxy/proxy.h
src/proxy/GmcpModule.h
src/proxy/GmcpMessage.h
src/proxy/UserTelnet.cpp
src/proxy/UserTelnet.h
src/preferences/mumeprotocolpage.cpp
src/preferences/mumeprotocolpage.h
src/preferences/mumeprotocolpage.ui
src/configuration/configuration.cpp
src/configuration/configuration.h
GMCP/game parsing integration and fallback raw text observation
  • Emit a new sig_rawGameText signal from MumeXmlParser whenever a line is sent to the user and connect it in Proxy to GameObserver for fallback parsing use cases
  • Add GameObserver::observeRawGameText and corresponding Signal2 member for downstream consumers
  • Ensure GMCP Char.Vitals parsing updates MapData prompt flags immediately for real-time lighting/status propagation
src/parser/mumexmlparser.cpp
src/parser/mumexmlparser.h
src/observer/gameobserver.cpp
src/observer/gameobserver.h
src/parser/mumexmlparser-gmcp.cpp
src/proxy/proxy.cpp
Clock/season tracking enhancements
  • Extend MumeClock to track last emitted season and emit a sig_seasonChanged signal whenever the inferred season changes
  • Trigger season-change emission from time, weather, and clock parsing paths after updating m_mumeStartEpoch
src/clock/mumeclock.cpp
src/clock/mumeclock.h
Configuration model extensions for canvas, hotkeys, communications, and clock GMCP settings
  • Introduce conversion helpers and persistent config keys for texture sets, seasonal textures, layer transparency, and extended canvas background/marker visibility options, including a VisibilityFilter helper with callbacks
  • Add new Hotkeys and CommsSettings subgroups with NamedConfig-backed options and corresponding read/write implementations wired into configuration grouping callbacks
  • Extend MumeClockSettings with NamedConfig fields (gmcpBroadcast, gmcpBroadcastInterval) and a default constructor; update read/write to persist them and add a yell fallback parsing toggle on General settings
src/configuration/configuration.cpp
src/configuration/configuration.h
src/map/infomark.h (included via forward decl usage)
Mudlet example script for consuming MMapper GMCP clock data
  • Introduce mudlet_clock_example.lua demonstrating how to enable MUME.Time via GMCP, consume MUME.Time.Info, maintain a local clock state, and raise Mudlet events on clock/time-of-day updates
  • Provide helper functions for formatting MUME time, computing weekdays, and rendering human-readable time/season/moon information plus a user alias (mmtime) for inspection
mudlet_clock_example.lua

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • There is quite a bit of unconditional qDebug logging added in the GMCP and clock-broadcast paths (e.g. in UserTelnet, Proxy::startClockBroadcaster/broadcastClockInfo); consider reducing verbosity or gating these behind an existing debug flag to avoid noisy logs in normal use.
  • The season-change notification logic in MumeClock is duplicated across parseMumeTime, parseWeather, and parseClockTime; consider extracting a small helper (e.g. updateAndEmitSeasonChange(moment)) to centralize this behavior and reduce the risk of future inconsistencies.
  • This PR introduces a number of configuration additions (textures, hotkeys, comms, visibility filters) that are orthogonal to GMCP/clock synchronization; if possible, consider splitting these into a separate change to keep the GMCP/clock work more focused and easier to review.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There is quite a bit of unconditional qDebug logging added in the GMCP and clock-broadcast paths (e.g. in UserTelnet, Proxy::startClockBroadcaster/broadcastClockInfo); consider reducing verbosity or gating these behind an existing debug flag to avoid noisy logs in normal use.
- The season-change notification logic in MumeClock is duplicated across parseMumeTime, parseWeather, and parseClockTime; consider extracting a small helper (e.g. updateAndEmitSeasonChange(moment)) to centralize this behavior and reduce the risk of future inconsistencies.
- This PR introduces a number of configuration additions (textures, hotkeys, comms, visibility filters) that are orthogonal to GMCP/clock synchronization; if possible, consider splitting these into a separate change to keep the GMCP/clock work more focused and easier to review.

## Individual Comments

### Comment 1
<location> `src/proxy/proxy.cpp:915-924` </location>
<code_context>
+void Proxy::startClockBroadcaster()
</code_context>

<issue_to_address>
**issue (bug_risk):** Validate GMCP clock interval to avoid zero/negative or extremely small timer periods.

`gmcpBroadcastInterval` is used directly in `QTimer::setInterval`. If the config (now or in the future) allows 0/negative or very small values, this could produce undefined behaviour or an excessively fast timer. Please clamp the value to a sensible min (e.g. 100 ms or 2500 ms) and consider a max to prevent accidental flooding or effectively disabled timers.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +915 to +924
void Proxy::startClockBroadcaster()
{
const auto &config = getConfig();

qDebug() << "=== startClockBroadcaster called ===";
qDebug() << " gmcpBroadcast config:" << config.mumeClock.gmcpBroadcast.get();
qDebug() << " MUME_TIME enabled:" << isUserGmcpModuleEnabled(GmcpModuleTypeEnum::MUME_TIME);

// Only start if GMCP broadcasting is enabled and client supports MUME.Time module
if (!config.mumeClock.gmcpBroadcast.get() || !isUserGmcpModuleEnabled(GmcpModuleTypeEnum::MUME_TIME)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Validate GMCP clock interval to avoid zero/negative or extremely small timer periods.

gmcpBroadcastInterval is used directly in QTimer::setInterval. If the config (now or in the future) allows 0/negative or very small values, this could produce undefined behaviour or an excessively fast timer. Please clamp the value to a sensible min (e.g. 100 ms or 2500 ms) and consider a max to prevent accidental flooding or effectively disabled timers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant