summaryrefslogtreecommitdiff
path: root/website/src/docs
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-08-05 04:56:51 +0900
committerGitHub <noreply@github.com>2026-08-05 04:56:51 +0900
commitce1a6123f7d48100ba3b216746127ba269fe21fb (patch)
tree4adc410cd8eb063e17035c184658b1045dd68ae4 /website/src/docs
parent82a592fe744ab4172c8f86fd2a71ea540575174a (diff)
parent004cf9308b5d98737125509adee08b9018cce964 (diff)
downloadLunaticChat-ce1a6123f7d48100ba3b216746127ba269fe21fb.tar.gz
LunaticChat-ce1a6123f7d48100ba3b216746127ba269fe21fb.tar.bz2
LunaticChat-ce1a6123f7d48100ba3b216746127ba269fe21fb.zip
Merge pull request #267 from m1sk9/update/development-and-websitev1.3.0
docs: Move release notes onto the documentation site and correct the docs against the implementation
Diffstat (limited to 'website/src/docs')
-rw-r--r--website/src/docs/configuration.md30
-rw-r--r--website/src/docs/developers/architecture.md10
-rw-r--r--website/src/docs/developers/engine.md12
-rw-r--r--website/src/docs/developers/platform-paper.md24
-rw-r--r--website/src/docs/features/admin.md17
-rw-r--r--website/src/docs/features/channel-chat.md17
-rw-r--r--website/src/docs/features/direct-message.md11
-rw-r--r--website/src/docs/features/japanese-conversion.md12
-rw-r--r--website/src/docs/features/velocity.md5
-rw-r--r--website/src/docs/permissions.md2
-rw-r--r--website/src/docs/reference/commands.md3
-rw-r--r--website/src/docs/reference/compatibility.md4
12 files changed, 112 insertions, 35 deletions
diff --git a/website/src/docs/configuration.md b/website/src/docs/configuration.md
index 78430f3..f2cfe5b 100644
--- a/website/src/docs/configuration.md
+++ b/website/src/docs/configuration.md
@@ -6,6 +6,22 @@ layout: doc
LunaticChat's configuration is managed in `plugins/LunaticChat/config.yml`. A default configuration file is generated on the server's first startup.
+## Applying Changes
+
+There is no reload command. Edit `config.yml` and **restart the server** to apply a change.
+
+Boolean settings accept `true` / `false`, and also the `yes` / `no` / `on` / `off` spellings that Bukkit accepted historically, so a file written for an older release keeps working as it did.
+
+## Recovery From an Invalid File <Badge type="tip" text="v1.3.0~" />
+
+A `config.yml` the plugin cannot use never stops the plugin from starting.
+
+- If a **single value** cannot be read, only that setting falls back to its default, and a warning naming the key is logged. Every other setting in the file is still honoured.
+- If the file is **not valid YAML at all**, or cannot be read from disk, every setting falls back to its default and an error is logged.
+- A file containing only comments is a valid way of saying "use the defaults" and is not reported as a problem.
+
+Check the server log after editing `config.yml`: a setting that quietly reverted to its default was reported there.
+
## Global Settings
| Key | Type | Default | Description |
@@ -68,6 +84,20 @@ LunaticChat's configuration is managed in `plugins/LunaticChat/config.yml`. A de
| `channelMessageFormat` | `§7[§b#{channel}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{channel}` |
| `crossServerGlobalChatFormat` | `§7[§6{server}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{server}` |
+## Data Files
+
+Everything the plugin writes lives under `plugins/LunaticChat/`.
+
+| File | Written when | Notes |
+|------|--------------|-------|
+| `config.yml` | Generated on first startup | Never rewritten by the plugin |
+| `player-settings.yaml` | A player changes a setting with `/lc settings` | Path configurable via `userSettingsFilePath`. If it cannot be read at startup, **every player's settings fall back to their defaults** |
+| `channels.json` | Channels or memberships change | Only when channel chat is enabled |
+| `conversion_cache.json` | Periodically, per `cache.saveIntervalSeconds` | Only when Japanese conversion is enabled. Path configurable via `cache.filePath` |
+| `logs/channelchat/` | Per channel message | Only when message logging is enabled. See [Message Logging](/docs/features/message-logging) |
+
+Saves are coalesced rather than written on every change, and every file is written atomically, so nothing ever reads a half-written file. All of them are also flushed when the server stops.
+
## Default Configuration File
[View on GitHub](https://github.com/m1sk9/LunaticChat/blob/main/platform-paper/src/main/resources/config.yml)
diff --git a/website/src/docs/developers/architecture.md b/website/src/docs/developers/architecture.md
index 4ba88d1..0532594 100644
--- a/website/src/docs/developers/architecture.md
+++ b/website/src/docs/developers/architecture.md
@@ -34,15 +34,11 @@ Things that break unless Paper and Velocity share the exact same definition.
- `exception` — the shared vocabulary of domain errors
- `permission`, `command` — neutral abstractions for permission node strings and command results
-#### (b) Platform-independent pure logic
-
-Logic that could live anywhere, but is pulled into the neutral core because it is pure and reusable.
-
-- `converter` — the pure romaji-conversion algorithm (Trie) plus an external API client
+Everything in `engine` falls into this category. Logic that merely *could* live anywhere is not pulled in for that reason alone: romaji conversion used to sit here as "platform-independent pure logic", and moving it into `platform-paper` — where its only caller is — let `engine` shed its Ktor dependency, which the Velocity build had been paying for in JAR size for nothing.
The primary goal of centralizing (a) in `engine` is to create a **single source of truth for the wire contract**. Paper and Velocity are two artifacts built, deployed, and versioned separately; duplicating the protocol in both modules would inevitably drift. With a single definition in `engine`, a contract mismatch surfaces early as a compile error or a snapshot-test failure rather than a runtime mismatch in production.
-`engine` depends on no Bukkit / Velocity API, and borrows only the "meaning of types and values" from Adventure / Brigadier to avoid depending on their runtimes (`compileOnly` Adventure, and `toBrigadierResult()` returning an `Int` without depending on Brigadier itself). This lets `engine` be tested on a pure JVM without spinning up a Minecraft server, while platform concerns (the Folia scheduler, etc.) stay isolated in the platform modules.
+`engine` depends on no Bukkit / Velocity / Adventure / Brigadier API at all — its single dependency is `kotlinx-serialization-json`. Rendering was pushed out to the platform modules (`CommandResult` carries a message key, and `toBrigadierResult()` returns an `Int` without depending on Brigadier), so `engine` borrows nothing from a platform runtime. This lets `engine` be tested on a pure JVM without spinning up a Minecraft server, while platform concerns (the Folia scheduler, HTTP, Adventure components) stay isolated in the platform modules.
## Compatibility via the protocol version
@@ -77,7 +73,7 @@ For details, see [platform-paper - Paper / Folia Plugin](/docs/developers/platfo
4. **Annotation-driven commands** — `@Command` / `@Permission` / `@PlayerOnly` are read via Kotlin reflection and mapped onto the Brigadier tree. A command's definition and its metadata (permission, aliases) are declared together in one place.
5. **Folia compatibility** — asynchronous work runs on `asyncScheduler` and `PluginCoroutineScope` (SupervisorJob), and Bukkit API calls are moved back to the main thread via `scheduler.runTask`. Thread boundaries are handled explicitly so it also works on region-threaded Folia.
6. **Persistence chosen per purpose** — languages / player settings = KAML (YAML), channels / conversion cache = kotlinx.serialization JSON, channel logs = NDJSON. All follow the same pattern: in-memory cache + asynchronous save (debounce/queue) + synchronous save on shutdown.
-7. **DM/channel = local, global = via the proxy** — routing differs by chat type; only global chat goes through Velocity. The relay prevents loops in two stages: "exclude the source server" + "deduplicate by messageId".
+7. **Channel = local, global and DM = optionally via the proxy** — routing differs by chat type. Channel chat is always server-local; global chat crosses the proxy when `crossServerGlobalChat` is on, and direct messages do when `crossServerDirectMessage` is on. The relay prevents loops in two stages: "exclude the source server" + "deduplicate by messageId".
## Module details
diff --git a/website/src/docs/developers/engine.md b/website/src/docs/developers/engine.md
index ee3a2c1..a65a3f6 100644
--- a/website/src/docs/developers/engine.md
+++ b/website/src/docs/developers/engine.md
@@ -48,13 +48,11 @@ The compatibility check is "**MAJOR matches exactly, the remote MINOR is within
As a consequence of this design, Paper and Velocity can be released independently. See [Build, Release & Versioning](/docs/developers/resource#independent-versioning).
-## converter — Romaji-to-Japanese conversion
+## Romaji conversion is no longer here
-`converter` is not a Paper↔Velocity contract (Velocity does no romaji conversion); it lives in `engine` **because it is platform-independent pure logic**. It has three layers.
+Romaji conversion used to live in `engine` on the grounds that it was platform-independent pure logic. It now lives in [platform-paper](/docs/developers/platform-paper), which is its only caller.
-- `KanaConverter` (`object`) — converts romaji to hiragana with a **Trie**. An immutable structure of `sealed class TrieNode { Leaf, Branch }` covers mappings from 4 characters (`xtsu`→っ) down to 1 (`a`→あ). `isValidRomaji()` validates before conversion; `toHiragana()` is a pure algorithm using longest-match plus sokuon handling
-- `GoogleIMEClient` — receives a Ktor `HttpClient` via DI and converts hiragana to kanji-kana via Google IME (`langpair=ja-Hira|ja`), concatenating the top candidate of each segment of the response
-- `CacheData` (`@Serializable`) — the persistence schema for conversion results (`version` plus `entries: Map`). It is a container for caching the expensive IME conversions; the caching logic itself lives on the paper side
+Being platform-independent turned out not to be reason enough: keeping it here made `engine` depend on Ktor, and because both platforms depend on `engine`, the **Velocity** artifact shipped an HTTP client it never called. Moving it out is what let `engine` narrow its dependencies to `kotlinx-serialization-json` alone.
## chat/channel — Channel domain model
@@ -79,14 +77,14 @@ There are two UUID serializers because they serve different purposes. `UUIDSeria
## exception — Shared error vocabulary
-So that Paper and Velocity can handle domain errors as the same types, exceptions are centralized in `engine`. There is no common sealed base — it is a flat structure (23 types) that directly extends `Exception`. They fall into existence/reference, state, limit, and permission/BAN/KICK categories, and many take `playerId` / `channelId` / `limit` in the constructor and build their own messages. Because there is no base type, callers are expected to catch each individually.
+So that Paper and Velocity can handle domain errors as the same types, exceptions are centralized in `engine`. There is no common sealed base — it is a flat structure that directly extends `Exception`. They fall into existence/reference, state, limit, and permission/BAN/KICK categories, and many take `playerId` / `channelId` / `limit` in the constructor and build their own messages. Because there is no base type, callers are expected to catch each individually.
## permission / command — Neutral abstractions
Permissions and command results are placed in `engine` as neutral representations that can be passed to either the Bukkit or Velocity API.
- `LunaticChatPermissionNode` — permissions enumerated type-safely as `sealed class` + `object` subclasses. The string node can be passed to either platform's permission API, and `when` also gives exhaustiveness checking
-- `CommandResult` — a `sealed class` (`Success` / `SuccessWithMessage` / `Failure` / `InvalidUsage`). The message is an Adventure `Component`, and `toBrigadierResult()` expresses only "the meaning of the return value" (success=1/failure=0) without depending on Brigadier itself
+- `CommandResult` — a `sealed class` (`Success` / `SuccessWithMessage` / `Failure` / `InvalidUsage`). Messages travel as plain `String`, so no Adventure type reaches `engine`; turning them into styled output is the platform's job. `toBrigadierResult()` expresses only "the meaning of the return value" (success=1/failure=0) without depending on Brigadier itself
## Related
diff --git a/website/src/docs/developers/platform-paper.md b/website/src/docs/developers/platform-paper.md
index 19437c1..dbba4f1 100644
--- a/website/src/docs/developers/platform-paper.md
+++ b/website/src/docs/developers/platform-paper.md
@@ -121,7 +121,7 @@ Branches:
### Direct messages (DirectMessageHandler)
-Manages `/tell`・`/reply` state. Two `ConcurrentHashMap`s, `lastMessager` / `lastRecipient`, track reply targets, and `getReplyTarget()` returns an online player in the order "whoever messaged me → whoever I messaged".
+Manages `/tell`・`/reply` state. Two `ConcurrentHashMap`s, `lastMessager` / `lastRecipient`, track reply targets as a `sealed interface ReplyTarget` of `Local` (a UUID) or `Remote` (a player name plus server name). `getReplyTarget()` resolves in the order "whoever messaged me → whoever I messaged", validating as it goes: a `Local` target must be online, and a `Remote` target must still be reported on that server by `RemotePlayerRegistry`.
`sendDirectMessage()` applies romaji conversion per the sender's settings → delivers a hover-annotated copy to spy players (excluding sender and recipient) → sends the formatted message to sender and recipient plus a notification sound (settings-dependent). The message carries a `ClickEvent.suggestCommand` that fills in `/tell <sender>`.
@@ -144,27 +144,27 @@ Channel state itself is managed by the `chat/channel` package.
## config
-- `ConfigManager` — reads the main `config.yml` from **Bukkit's `FileConfiguration`** by dotted keys and hand-assembles `LunaticChatConfiguration` (note: this path is not KAML)
+- `ConfigManager` — deserializes `config.yml` into `LunaticChatConfiguration` with **KAML**, so each default lives in exactly one place: on the data class. It replaced a hand-written dotted-key mapper that repeated every default a second time, and they had already drifted — `checkForUpdates` disagreed with both `config.yml` and the data class, and the whole `messageLogging` block was documented but never read
+- Failure is handled per setting, not per file: on a `YamlException` the offending key is pruned from the document and decoding is retried, so one unreadable value costs only itself. Only a document that is not YAML at all falls back to defaults wholesale, and neither case is allowed to throw out of `onEnable`
+- `LenientBoolean` — a `Boolean` typealias with a serializer that still accepts `yes` / `no` / `on` / `off`. Bukkit read `config.yml` as YAML 1.1, where those are booleans; kaml reads YAML 1.2, where they are plain strings, and silently resetting them would have flipped `checkForUpdates: no` to its opposite default
- Feature defaults: `quickReplies=true`, `japaneseConversion=false`, `channelChat=false`, `velocityIntegration=false`
- Under `config/key`: `FeaturesConfig` / `ChannelChatFeatureConfig` / `JapaneseConversionFeatureConfig` / `VelocityIntegrationConfig` / `QuickRepliesFeatureConfig` / `MessageFormatConfig` / `ChannelMessageLoggingConfig`
-::: warning Implementation note
-`ChannelChatFeatureConfig.messageLogging` is not loaded by `ConfigManager` and stays at its default values (enabled=true, retention=30, 100MB). Whether this is intentional needs confirmation — decide whether to fix it or document it as intended behavior.
-:::
-
## i18n
- `Language` (enum) — `EN` / `JA`; unknown codes fall back to EN
- `LanguageManager` — loads `resources/languages/` with KAML at startup and flattens the nested YAML into dotted keys (`toggle.on`, etc.). `getMessage(key, placeholders)` resolves with selected-language → EN fallback and substitutes `{placeholder}`, returning the key itself if not found. A missing EN is a fatal error
- `MessageFormatter` (`object`) — produces an Adventure `Component` with a `[LC]` prefix and highlights `{braces}` placeholders detected by regex
-## converter (paper side) — engine integration
+## converter — Romaji-to-Japanese conversion
-The paper side handles the platform concerns of "cache management, timeouts, Bukkit scheduling", and delegates the conversion algorithm and API calls to `engine`.
+Romaji conversion lives here in full: the algorithm, the API client, the cache, and the platform concerns (timeouts, scheduling). It used to sit in `engine` as platform-independent pure logic, but `platform-paper` is its only caller, and keeping it in `engine` made the Velocity artifact carry Ktor for nothing.
-- `RomanjiConverter` — the two-stage conversion orchestrator. Per word: cache lookup → engine `KanaConverter` for romaji→hiragana → engine `GoogleIMEClient` for hiragana→kanji. Falls back to hiragana on API failure
-- `ConversionCache` — persists engine `CacheData` as JSON. In-memory cache plus debounced save (a FIXME notes that eviction on `maxEntries` overflow is effectively random due to `ConcurrentHashMap` ordering)
-- `RomajiConversionHelper` — `convertWithRomaji()`. Calls synchronously via `runBlocking` + `withTimeoutOrNull` (default 1000ms), returning `"original §e(converted)"` on success and the original text on failure/timeout
+- `KanaConverter` (`object`) — romaji to hiragana with a **Trie**. An immutable `sealed class TrieNode { Leaf, Branch }` covers mappings from 4 characters (`xtsu`→っ) down to 1 (`a`→あ). `isValidRomaji()` validates before conversion; `toHiragana()` is a pure longest-match algorithm with sokuon handling
+- `GoogleIMEClient` — receives a Ktor `HttpClient` via DI and converts hiragana to kanji-kana via Google IME (`langpair=ja-Hira|ja`), concatenating the top candidate of each segment
+- `RomanjiConverter` — the two-stage orchestrator. Per word: cache lookup → `KanaConverter` → `GoogleIMEClient`. Words are converted concurrently, and an API failure degrades to hiragana rather than failing the message
+- `ConversionCache` — persists `CacheData` as JSON. In-memory cache plus debounced save (a FIXME notes that eviction on `maxEntries` overflow drops an arbitrary 10%, not the oldest, because `ConcurrentHashMap` is unordered)
+- `RomajiConversionHelper` — `convertWithRomaji()` is `suspend` and bounded by `withTimeoutOrNull` (default 1000ms), returning `"original §e(converted)"` on success and the original text on failure or timeout. `convertWithRomajiBlocking()` wraps it in `runBlocking` for `AsyncChatEvent`, the one caller that must decide whether to cancel the event before returning; command handlers run on the tick thread and must use the suspending form
## Velocity integration (Paper side)
@@ -177,7 +177,7 @@ Using the engine's protocol, it communicates with the proxy over Bukkit's Plugin
## settings / common
- `PlayerSettingsManager` — manages three boolean settings in `ConcurrentHashMap`s. Uses the engine DTOs; unset values default to true
-- `YamlPlayerSettingsStorage` — reads/writes `player-settings.yaml` with KAML. Recovers from a backup on load failure; debounced save (5s)
+- `YamlPlayerSettingsStorage` — reads/writes `player-settings.yaml` with KAML; debounced save (5s). There is no backup file: a load failure is logged and falls back to **empty settings**, which means every player silently returns to defaults
- `UpdateChecker` — hits the GitHub Releases API via Ktor and compares semver. The result is a sealed `UpdateCheckResult`
- `SoundCollector` — Adventure `Sound` constants for notifications plus Player extension functions
- `PermissionCollector` — a DSL that collects permissions via `@PermissionDsl` + the `+LunaticChatPermissionNode` operator. `requirePermission` throws the engine's `RequirePermissionException`
diff --git a/website/src/docs/features/admin.md b/website/src/docs/features/admin.md
index c3a3d2f..3f71dcc 100644
--- a/website/src/docs/features/admin.md
+++ b/website/src/docs/features/admin.md
@@ -24,9 +24,11 @@ Displayed information:
## Spy Mode
-Players with the `lunaticchat.spy` permission (default: op) can view all direct messages sent and received on the server.
+Players with the `lunaticchat.spy` permission (default: op) can view both the direct messages and the channel messages sent on the server.
-- Spy players see the original message before romaji conversion
+- Direct messages are delivered to spies, excluding the sender and the recipient
+- Channel messages are delivered to spies, excluding the sender and the channel's own members
+- For direct messages, spies see the original text before romaji conversion. Channel messages reach spies in the same converted form the members see
- Hover text indicates the message is a spy message
- Spy players themselves are not included in the normal sender/recipient list
@@ -46,6 +48,15 @@ When `checkForUpdates` is `true` (default), the plugin checks for new versions a
checkForUpdates: true
```
+## Nightly Builds
+
+Builds produced from the `main` branch outside of a release are marked as nightly, and the plugin says so rather than letting it go unnoticed.
+
+- Every player is warned on join that the build may be unstable, along with a pointer to GitHub Issues
+- `/lc status` shows the same warning, and displays the release channel in yellow instead of green
+
+Nightly builds are not covered by the [security policy](https://github.com/m1sk9/LunaticChat/blob/main/.github/SECURITY.md); use a release build on a production server.
+
## Debug Mode
Setting `debug` to `true` enables verbose plugin logging. This is useful for troubleshooting issues or submitting bug reports.
@@ -68,7 +79,7 @@ language: "ja" # "en" or "ja"
| Permission | Default | Description |
|-----------|---------|-------------|
-| `lunaticchat.spy` | op | View all direct messages |
+| `lunaticchat.spy` | op | View all direct and channel messages |
| `lunaticchat.channelbypass` | op | Bypass channel restrictions |
| `lunaticchat.noticeupdate` | op | Receive update notifications |
| `lunaticchat.command.lcv.status` | op | Use the `/lcv status` command |
diff --git a/website/src/docs/features/channel-chat.md b/website/src/docs/features/channel-chat.md
index c06ea39..ff6bab0 100644
--- a/website/src/docs/features/channel-chat.md
+++ b/website/src/docs/features/channel-chat.md
@@ -37,6 +37,19 @@ Players can join multiple channels, but only one channel can be active at a time
/lc channel status # Display the current active channel and list of joined channels
```
+## Sending to Global Chat (`!` Prefix)
+
+While a channel is active, your chat goes to that channel. Prefixing a message with `!` sends that one message to global chat instead, without leaving or switching the channel.
+
+```
+!Hello everyone # Goes to global chat even while a channel is active
+```
+
+The `!` and any space following it are stripped before the message is sent, so it never appears in the message itself. A message consisting of only `!` is discarded and nothing is sent.
+
+> [!NOTE]
+> The prefix is handled by the chat listener, which is only registered when channel chat, cross-server global chat, or Japanese conversion is enabled. If all three are disabled, a leading `!` stays in the message exactly as typed.
+
## Roles and Permissions
Channels have three roles.
@@ -87,6 +100,10 @@ See the `features.channelChat.messageLogging` section on the [Configuration page
Players with the `lunaticchat.channelbypass` permission (default: op) are protected from kicks and bans, and can force-delete channels.
+## Spy Mode
+
+Channel messages are not private from administrators. Players with the `lunaticchat.spy` permission (default: op) also receive channel messages, excluding the sender and the channel's own members. See [Spy Mode](/docs/features/admin#spy-mode) for details.
+
## Message Format
The display format for channel messages can be customized via `messageFormat.channelMessageFormat` in `config.yml`. See [Message Format](/docs/reference/message-format) for details.
diff --git a/website/src/docs/features/direct-message.md b/website/src/docs/features/direct-message.md
index 07f6cdb..ec0f24e 100644
--- a/website/src/docs/features/direct-message.md
+++ b/website/src/docs/features/direct-message.md
@@ -42,6 +42,17 @@ To message a player on another server, specify the player argument as `playerNam
/tell <player>@<server> <message>
```
+`serverName` is the name of the destination server **as registered in your Velocity configuration** (`velocity.toml`), which is what the proxy resolves the target against. Tab completion offers the names and players it currently knows about.
+
+Set `features.velocityIntegration.serverName` on each backend to that same name. It does not affect routing, but it fills `{server}` in cross-server chat and is how a server recognises which players are its own — if it disagrees with the Velocity name, local players are treated as remote in tab completion.
+
+Delivery can fail in two ways, and the sender is told which:
+
+| Reason | Meaning |
+|--------|---------|
+| `SERVER_NOT_FOUND` | No server registered on the proxy has that name |
+| `TARGET_OFFLINE` | The server exists, but that player is not on it — including when they are online on a different server |
+
## Notification Settings
Players can individually control the sound notification when receiving direct messages.
diff --git a/website/src/docs/features/japanese-conversion.md b/website/src/docs/features/japanese-conversion.md
index ec1df1b..d98ac87 100644
--- a/website/src/docs/features/japanese-conversion.md
+++ b/website/src/docs/features/japanese-conversion.md
@@ -19,8 +19,11 @@ Conversion is performed in two stages.
Input: konnichiha sekai
Stage 1: こんにちは せかい
Stage 2: こんにちは 世界
+Sent: konnichiha sekai §e(こんにちは 世界)
```
+The original text is **not** replaced. What you typed is kept, and the conversion result is appended in parentheses, so both are visible to everyone who receives the message.
+
## Conversion Targets
- Normal chat
@@ -48,7 +51,7 @@ Conversion results are cached per word. When the same word is converted again, t
| `cache.saveIntervalSeconds` | `300` | Interval for saving to disk (seconds) |
| `cache.filePath` | `"conversion_cache.json"` | Path to the cache file |
-When the cache reaches its limit, the oldest 10% of entries are automatically removed.
+When the cache reaches its limit, 10% of the entries are removed. Which entries are dropped is not defined: the in-memory cache is unordered, so eviction is effectively arbitrary rather than oldest-first.
## API Settings
@@ -56,6 +59,11 @@ Settings related to the connection to the Google IME API.
| Setting Key | Default | Description |
|-------------|---------|-------------|
-| `api.timeout` | `3000` | Request timeout (milliseconds) |
+| `api.timeout` | `3000` | Timeout for a single API request (milliseconds) |
If the API times out or fails, the message is sent in hiragana as-is.
+
+> [!WARNING]
+> Independently of `api.timeout`, converting one message is given an overall budget of **1000 ms**. When that budget runs out the conversion is abandoned and the message is sent exactly as typed, with nothing appended.
+>
+> Because the overall budget is shorter than the default `api.timeout`, raising `api.timeout` above `1000` has no practical effect.
diff --git a/website/src/docs/features/velocity.md b/website/src/docs/features/velocity.md
index 8bc1b70..7842a24 100644
--- a/website/src/docs/features/velocity.md
+++ b/website/src/docs/features/velocity.md
@@ -51,6 +51,8 @@ When `crossServerGlobalChat` is set to `true`, player chat messages are relayed
Each message is assigned a unique ID, and a cache prevents the same message from being displayed more than once. The cache size can be configured with `messageDeduplicationCacheSize` (default: `100`).
+Entries expire 60 seconds after they are recorded. If the cache is still over its configured size after expired entries are cleared, the oldest remaining entries are dropped.
+
## Cross-Server Direct Messages <Badge type="tip" text="v1.3.0~" />
Setting `crossServerDirectMessage` to `true` lets players exchange direct messages with players on other servers connected to the same proxy.
@@ -80,7 +82,8 @@ The handshake timeout is 5 seconds. If the handshake times out, the state become
|-------------|---------|-------------|
| `enabled` | `false` | Enable Velocity integration |
| `crossServerGlobalChat` | `false` | Enable cross-server global chat |
-| `serverName` | `"Unknown"` | Server name displayed in cross-server chat |
+| `crossServerDirectMessage` | `false` | Enable cross-server direct messages |
+| `serverName` | `"Unknown"` | This server's own name. Fills `{server}` in cross-server chat and is how the server recognises its own players. Set it to the name registered in `velocity.toml` |
| `messageDeduplicationCacheSize` | `100` | Size of the message deduplication cache |
## Message Format
diff --git a/website/src/docs/permissions.md b/website/src/docs/permissions.md
index d059bba..caada8b 100644
--- a/website/src/docs/permissions.md
+++ b/website/src/docs/permissions.md
@@ -44,7 +44,7 @@ The following permissions are granted to OPs only by default.
| Permission | Default | Description |
|------------|---------|-------------|
-| `lunaticchat.spy` | op | View all direct messages on the server |
+| `lunaticchat.spy` | op | View all direct and channel messages on the server |
| `lunaticchat.noticeupdate` | op | Receive update notifications |
| `lunaticchat.channelbypass` | op | Bypass channel restrictions (kick/ban protection, force deletion) |
| `lunaticchat.command.lcv.status` | op | Use the `/lcv status` command |
diff --git a/website/src/docs/reference/commands.md b/website/src/docs/reference/commands.md
index a039f7a..28627bc 100644
--- a/website/src/docs/reference/commands.md
+++ b/website/src/docs/reference/commands.md
@@ -58,7 +58,8 @@ Creates a new channel. The creator becomes the owner.
- **Aliases**: `new`
- **Permission**: `lunaticchat.command.lc.channel.create`
-- `channelId`: Only alphanumeric characters, underscores, and hyphens are allowed
+- `channelId`: 3-30 characters; only alphanumeric characters, underscores, and hyphens are allowed
+- `name`: Cannot be blank
- `isPrivate`: `true` / `false` (default: `false`)
#### `/lc channel list [page]`
diff --git a/website/src/docs/reference/compatibility.md b/website/src/docs/reference/compatibility.md
index e0e341c..23274ab 100644
--- a/website/src/docs/reference/compatibility.md
+++ b/website/src/docs/reference/compatibility.md
@@ -57,9 +57,11 @@ The rules (from Velocity's perspective) are:
Compatibility is checked at connection time:
-1. The Paper server sends a handshake to Velocity at startup
+1. The Paper server sends a handshake to Velocity one second after the **first player joins**
2. Velocity validates Paper's protocol version against its own
3. On mismatch, Velocity rejects the connection and Paper's state becomes `FAILED`
4. The handshake timeout is 5 seconds
+The handshake is sent once per server start, and it is triggered by a player joining rather than by startup itself — the plugin messaging channel needs a player connection to send on. Until the first player joins, `/lcv status` reports `DISCONNECTED`, which is normal and not a sign of a problem.
+
Live connection state is available via `/lcv status`. See [Velocity Integration](/docs/features/velocity#connection-states) for details.