summaryrefslogtreecommitdiff
path: root/website
diff options
context:
space:
mode:
Diffstat (limited to 'website')
-rw-r--r--website/.vitepress/config/en.ts4
-rw-r--r--website/.vitepress/config/ja.ts4
-rw-r--r--website/.vitepress/theme/components/CompatibilityMatrix.vue313
-rw-r--r--website/.vitepress/theme/components/DownloadCard.vue20
-rw-r--r--website/.vitepress/theme/components/useCompatibilityData.ts167
-rw-r--r--website/.vitepress/theme/index.ts2
-rw-r--r--website/src/docs/features/velocity.md55
-rw-r--r--website/src/docs/reference/compatibility.md57
-rw-r--r--website/src/en/docs/features/velocity.md53
-rw-r--r--website/src/en/docs/reference/compatibility.md57
-rw-r--r--website/src/en/index.md13
-rw-r--r--website/src/index.md13
12 files changed, 676 insertions, 82 deletions
diff --git a/website/.vitepress/config/en.ts b/website/.vitepress/config/en.ts
index 3a05016..6bc755b 100644
--- a/website/.vitepress/config/en.ts
+++ b/website/.vitepress/config/en.ts
@@ -67,6 +67,10 @@ export const en: DefaultTheme.Config = {
link: '/en/docs/reference/player-settings',
text: 'Player Settings',
},
+ {
+ link: '/en/docs/reference/compatibility',
+ text: 'Paper / Velocity Compatibility',
+ },
],
},
],
diff --git a/website/.vitepress/config/ja.ts b/website/.vitepress/config/ja.ts
index e74102c..2335509 100644
--- a/website/.vitepress/config/ja.ts
+++ b/website/.vitepress/config/ja.ts
@@ -67,6 +67,10 @@ export const ja: DefaultTheme.Config = {
link: '/docs/reference/player-settings',
text: 'プレイヤー設定',
},
+ {
+ link: '/docs/reference/compatibility',
+ text: 'Paper / Velocity 互換性',
+ },
],
},
],
diff --git a/website/.vitepress/theme/components/CompatibilityMatrix.vue b/website/.vitepress/theme/components/CompatibilityMatrix.vue
new file mode 100644
index 0000000..0e91edc
--- /dev/null
+++ b/website/.vitepress/theme/components/CompatibilityMatrix.vue
@@ -0,0 +1,313 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useData } from 'vitepress';
+import {
+ useCompatibilityData,
+ checkCompatibility,
+ formatProtocol,
+ type CompatibilityResult,
+ type PlatformReleaseEntry,
+} from './useCompatibilityData';
+
+const { lang } = useData();
+const isEn = computed(() => lang.value === 'en-US');
+const { data, loading, error } = useCompatibilityData();
+
+const t = computed(() =>
+ isEn.value
+ ? {
+ loading: 'Loading compatibility data...',
+ error: 'Failed to load compatibility data. Please check ',
+ errorSuffix: ' directly.',
+ emptyPaper: 'No Paper releases yet.',
+ emptyVelocity: 'No Velocity releases yet.',
+ empty: 'No releases yet. Compatibility matrix will appear once releases are published.',
+ paperVersion: 'Paper version',
+ velocityVersion: 'Velocity version',
+ protocol: 'Protocol',
+ unknown: 'unknown',
+ compatible: 'Compatible',
+ incompatible: 'Incompatible',
+ compatibleShort: 'OK',
+ compatibilityHeader: 'Compatibility',
+ reasonMajorMismatch: 'Major version mismatch',
+ reasonPaperTooNew: 'Paper newer — update Velocity first',
+ reasonVelocityTooNew: 'Velocity newer — update Paper',
+ reasonPaperTooOld: 'Paper too old',
+ reasonVelocityTooOld: 'Velocity too old',
+ legend: 'Legend',
+ legendCompatible: 'Compatible — both can connect.',
+ legendIncompatible: 'Incompatible — handshake will be rejected.',
+ }
+ : {
+ loading: '互換性情報を取得中...',
+ error: '互換性情報の取得に失敗しました.',
+ errorSuffix: ' を直接ご確認ください.',
+ emptyPaper: 'Paper のリリースはまだありません.',
+ emptyVelocity: 'Velocity のリリースはまだありません.',
+ empty: 'リリースがまだありません.リリース後に互換性マトリクスが表示されます.',
+ paperVersion: 'Paper バージョン',
+ velocityVersion: 'Velocity バージョン',
+ protocol: 'プロトコル',
+ unknown: '不明',
+ compatible: '互換',
+ incompatible: '非互換',
+ compatibleShort: 'OK',
+ compatibilityHeader: '互換性',
+ reasonMajorMismatch: 'MAJOR バージョン不一致',
+ reasonPaperTooNew: 'Paper が新しすぎる — Velocity を先に更新',
+ reasonVelocityTooNew: 'Velocity が新しすぎる — Paper を更新',
+ reasonPaperTooOld: 'Paper が古すぎる',
+ reasonVelocityTooOld: 'Velocity が古すぎる',
+ legend: '凡例',
+ legendCompatible: '互換 — 接続可能.',
+ legendIncompatible: '非互換 — ハンドシェイクで拒否されます.',
+ },
+);
+
+const sortedPaper = computed<PlatformReleaseEntry[]>(() =>
+ [...data.value.paper].sort((a, b) => compareVersion(b.version, a.version)),
+);
+const sortedVelocity = computed<PlatformReleaseEntry[]>(() =>
+ [...data.value.velocity].sort((a, b) => compareVersion(b.version, a.version)),
+);
+
+function compareVersion(a: string, b: string): number {
+ const pa = a.split('.').map((n) => Number.parseInt(n, 10));
+ const pb = b.split('.').map((n) => Number.parseInt(n, 10));
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
+ const da = pa[i] ?? 0;
+ const db = pb[i] ?? 0;
+ if (da !== db) return da - db;
+ }
+ return 0;
+}
+
+function reasonLabel(result: CompatibilityResult): string {
+ switch (result) {
+ case 'major-mismatch':
+ return t.value.reasonMajorMismatch;
+ case 'paper-too-new':
+ return t.value.reasonPaperTooNew;
+ case 'velocity-too-new':
+ return t.value.reasonVelocityTooNew;
+ case 'paper-too-old':
+ return t.value.reasonPaperTooOld;
+ case 'velocity-too-old':
+ return t.value.reasonVelocityTooOld;
+ default:
+ return '';
+ }
+}
+
+function cellResult(
+ paper: PlatformReleaseEntry,
+ velocity: PlatformReleaseEntry,
+): { ok: boolean; reason: string } {
+ if (!paper.protocol || !velocity.protocol) {
+ return { ok: false, reason: t.value.unknown };
+ }
+ const r = checkCompatibility(paper.protocol, velocity.protocol);
+ return { ok: r === 'compatible', reason: r === 'compatible' ? '' : reasonLabel(r) };
+}
+</script>
+
+<template>
+ <div class="compat-matrix">
+ <div v-if="loading" class="compat-loading">
+ <p>{{ t.loading }}</p>
+ </div>
+
+ <div v-else-if="error" class="compat-error">
+ <p>
+ {{ t.error
+ }}<a href="https://github.com/m1sk9/LunaticChat/releases" target="_blank" rel="noopener">GitHub Releases</a>{{ t.errorSuffix }}
+ </p>
+ </div>
+
+ <div v-else-if="sortedPaper.length === 0 && sortedVelocity.length === 0" class="compat-empty">
+ <p>{{ t.empty }}</p>
+ </div>
+
+ <div v-else class="compat-content">
+ <div class="compat-table-wrapper">
+ <table class="compat-table">
+ <thead>
+ <tr>
+ <th class="compat-corner">
+ <span class="compat-axis-paper">{{ t.paperVersion }}</span>
+ <span class="compat-axis-divider">/</span>
+ <span class="compat-axis-velocity">{{ t.velocityVersion }}</span>
+ </th>
+ <th v-for="v in sortedVelocity" :key="v.tag" class="compat-col-header">
+ <div class="compat-version">v{{ v.version }}</div>
+ <div class="compat-protocol">
+ {{ t.protocol }}: {{ v.protocol ? formatProtocol(v.protocol) : t.unknown }}
+ </div>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-if="sortedPaper.length === 0">
+ <td colspan="100" class="compat-empty-cell">{{ t.emptyPaper }}</td>
+ </tr>
+ <tr v-for="p in sortedPaper" :key="p.tag">
+ <th scope="row" class="compat-row-header">
+ <div class="compat-version">v{{ p.version }}</div>
+ <div class="compat-protocol">
+ {{ t.protocol }}: {{ p.protocol ? formatProtocol(p.protocol) : t.unknown }}
+ </div>
+ </th>
+ <td
+ v-for="v in sortedVelocity"
+ :key="v.tag"
+ :class="['compat-cell', cellResult(p, v).ok ? 'compat-ok' : 'compat-ng']"
+ :title="cellResult(p, v).reason || t.compatible"
+ >
+ <span v-if="cellResult(p, v).ok" class="compat-mark-ok" :aria-label="t.compatible">✓</span>
+ <span v-else class="compat-mark-ng" :aria-label="t.incompatible">✗</span>
+ </td>
+ <td v-if="sortedVelocity.length === 0" class="compat-empty-cell">{{ t.emptyVelocity }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div class="compat-legend">
+ <p class="compat-legend-title">{{ t.legend }}</p>
+ <ul>
+ <li><span class="compat-mark-ok">✓</span> {{ t.legendCompatible }}</li>
+ <li><span class="compat-mark-ng">✗</span> {{ t.legendIncompatible }}</li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.compat-matrix {
+ margin: 24px 0;
+}
+
+.compat-loading,
+.compat-empty {
+ text-align: center;
+ padding: 32px 0;
+ color: var(--vp-c-text-2);
+}
+
+.compat-error {
+ border: 1px solid var(--vp-c-danger-soft);
+ background: var(--vp-c-danger-soft);
+ border-radius: 8px;
+ padding: 16px 20px;
+}
+
+.compat-error a {
+ color: var(--vp-c-brand-1);
+ text-decoration: underline;
+}
+
+.compat-table-wrapper {
+ overflow-x: auto;
+ border: 1px solid var(--vp-c-divider);
+ border-radius: 8px;
+}
+
+.compat-table {
+ border-collapse: collapse;
+ width: 100%;
+ margin: 0;
+ font-size: 0.875rem;
+}
+
+.compat-table th,
+.compat-table td {
+ border: 1px solid var(--vp-c-divider);
+ padding: 8px 12px;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.compat-corner {
+ background: var(--vp-c-bg-soft);
+ font-weight: 500;
+ font-size: 0.75rem;
+ white-space: nowrap;
+ text-align: left !important;
+}
+
+.compat-axis-paper,
+.compat-axis-velocity {
+ display: inline-block;
+}
+
+.compat-axis-divider {
+ margin: 0 4px;
+ color: var(--vp-c-text-3);
+}
+
+.compat-col-header,
+.compat-row-header {
+ background: var(--vp-c-bg-soft);
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.compat-version {
+ font-size: 0.95rem;
+}
+
+.compat-protocol {
+ font-size: 0.7rem;
+ color: var(--vp-c-text-2);
+ font-weight: 400;
+ margin-top: 2px;
+}
+
+.compat-cell {
+ font-size: 1.1rem;
+ font-weight: 700;
+}
+
+.compat-ok {
+ background: rgba(20, 200, 100, 0.08);
+}
+
+.compat-ng {
+ background: rgba(220, 60, 60, 0.06);
+}
+
+.compat-mark-ok {
+ color: rgb(20, 160, 90);
+}
+
+.compat-mark-ng {
+ color: rgb(200, 60, 60);
+}
+
+.compat-empty-cell {
+ color: var(--vp-c-text-3);
+ font-style: italic;
+}
+
+.compat-legend {
+ margin-top: 16px;
+ font-size: 0.85rem;
+ color: var(--vp-c-text-2);
+}
+
+.compat-legend-title {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.compat-legend ul {
+ margin: 0;
+ padding-left: 20px;
+}
+
+.compat-legend li {
+ line-height: 1.7;
+}
+</style>
diff --git a/website/.vitepress/theme/components/DownloadCard.vue b/website/.vitepress/theme/components/DownloadCard.vue
index a03adfb..4337181 100644
--- a/website/.vitepress/theme/components/DownloadCard.vue
+++ b/website/.vitepress/theme/components/DownloadCard.vue
@@ -29,8 +29,11 @@ const t = computed(() =>
'The latest build from the main branch is available from CI. Development builds are not guaranteed to be stable.',
viewCiBuilds: 'View latest CI builds',
compatNotice:
- 'Paper and Velocity plugins are versioned with a protocol version. For MINOR version changes, update Velocity first. For MAJOR version changes, update all servers simultaneously.',
- compatLink: 'See Velocity Integration - Protocol Version for details.',
+ 'Using the latest Paper and the latest Velocity always works. If you need to mix older versions, check the compatibility matrix below.',
+ compatLink: 'See Paper / Velocity Compatibility for details.',
+ compatMatrixTitle: 'Compatibility Matrix',
+ compatMatrixDesc:
+ 'Combinations marked ✓ can connect. Hover a cell for details.',
spigotNotice:
'LunaticChat only supports Paper / Folia servers. It does not work on Spigot or BungeeCord, and there are no plans to support them in the future.',
spigotAlt:
@@ -57,8 +60,11 @@ const t = computed(() =>
'最新の main ブランチのビルドは CI から取得できます。開発ビルドは安定性が保証されていません。',
viewCiBuilds: '最新の CI ビルドを確認',
compatNotice:
- 'Paper プラグインと Velocity プラグインはプロトコルバージョンで互換性が管理されています.MINOR バージョン変更時は Velocity を先にアップデートしてください.MAJOR バージョン変更時は全サーバーを同時にアップデートする必要があります.',
- compatLink: '詳細は Velocity 連携 - プロトコルバージョン を参照してください.',
+ '両プラグインの最新版同士は常に互換性があります.古いバージョンを混ぜる場合のみ,下の互換性マトリクスを確認してください.',
+ compatLink: '詳細は Paper / Velocity 互換性 を参照してください.',
+ compatMatrixTitle: '互換性マトリクス',
+ compatMatrixDesc:
+ '✓ の組み合わせは接続可能です.セルにホバーすると詳細が表示されます.',
spigotNotice:
'LunaticChat は Paper / Folia サーバーのみをサポートしています.Spigot / BungeeCord では動作せず,今後も対応予定はありません.',
spigotAlt:
@@ -107,7 +113,7 @@ function formatDate(dateStr: string | null): string {
<div class="download-compat-notice">
<p class="download-compat-title">{{ t.compatible }}</p>
<p>{{ t.compatNotice }}</p>
- <p><a :href="isEn ? '/en/docs/features/velocity#protocol-version' : '/docs/features/velocity#プロトコルバージョン'">{{ t.compatLink }}</a></p>
+ <p><a :href="isEn ? '/en/docs/reference/compatibility' : '/docs/reference/compatibility'">{{ t.compatLink }}</a></p>
</div>
<div v-if="loading" class="download-loading">
@@ -188,6 +194,10 @@ function formatDate(dateStr: string | null): string {
</div>
</div>
+ <h2>{{ t.compatMatrixTitle }}</h2>
+ <p>{{ t.compatMatrixDesc }}</p>
+ <CompatibilityMatrix />
+
<h2>Modrinth</h2>
<p>{{ t.modrinthDesc }}</p>
<div class="download-ci">
diff --git a/website/.vitepress/theme/components/useCompatibilityData.ts b/website/.vitepress/theme/components/useCompatibilityData.ts
new file mode 100644
index 0000000..452e8b4
--- /dev/null
+++ b/website/.vitepress/theme/components/useCompatibilityData.ts
@@ -0,0 +1,167 @@
+import { ref, onMounted } from 'vue';
+
+const REPO = 'm1sk9/LunaticChat';
+const PROTOCOL_FILE_PATH =
+ 'engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersion.kt';
+
+interface GitHubRelease {
+ tag_name: string;
+ published_at: string;
+ html_url: string;
+}
+
+export interface ProtocolVersion {
+ major: number;
+ minor: number;
+ patch: number;
+ minSupportedMinor: number;
+}
+
+export interface PlatformReleaseEntry {
+ platform: 'paper' | 'velocity';
+ version: string;
+ tag: string;
+ publishedAt: string;
+ releaseUrl: string;
+ protocol: ProtocolVersion | null;
+}
+
+export interface CompatibilityData {
+ paper: PlatformReleaseEntry[];
+ velocity: PlatformReleaseEntry[];
+}
+
+function parseProtocolVersion(source: string): ProtocolVersion | null {
+ const match = (key: string): number | null => {
+ const re = new RegExp(`const\\s+val\\s+${key}\\s*=\\s*(\\d+)`);
+ const m = source.match(re);
+ return m ? Number.parseInt(m[1], 10) : null;
+ };
+
+ const major = match('MAJOR');
+ const minor = match('MINOR');
+ const patch = match('PATCH');
+ const minSupportedMinor = match('MIN_SUPPORTED_MINOR');
+
+ if (
+ major === null ||
+ minor === null ||
+ patch === null ||
+ minSupportedMinor === null
+ ) {
+ return null;
+ }
+
+ return { major, minor, patch, minSupportedMinor };
+}
+
+async function fetchProtocolAtTag(tag: string): Promise<ProtocolVersion | null> {
+ const url = `https://raw.githubusercontent.com/${REPO}/${encodeURIComponent(tag)}/${PROTOCOL_FILE_PATH}`;
+ try {
+ const res = await fetch(url);
+ if (!res.ok) return null;
+ const text = await res.text();
+ return parseProtocolVersion(text);
+ } catch {
+ return null;
+ }
+}
+
+function buildEntry(
+ release: GitHubRelease,
+ platform: 'paper' | 'velocity',
+): PlatformReleaseEntry {
+ const version = release.tag_name.replace(/^(paper\/|velocity\/)?v/, '');
+ return {
+ platform,
+ version,
+ tag: release.tag_name,
+ publishedAt: release.published_at,
+ releaseUrl: release.html_url,
+ protocol: null,
+ };
+}
+
+export function useCompatibilityData() {
+ const data = ref<CompatibilityData>({ paper: [], velocity: [] });
+ const loading = ref(true);
+ const error = ref(false);
+
+ onMounted(async () => {
+ try {
+ const res = await fetch(
+ `https://api.github.com/repos/${REPO}/releases?per_page=100`,
+ );
+ if (!res.ok) {
+ error.value = true;
+ return;
+ }
+
+ const releases: GitHubRelease[] = await res.json();
+
+ const isUnified = (tag: string) => /^v\d/.test(tag);
+ const isPaperTag = (tag: string) =>
+ tag.startsWith('paper/v') || isUnified(tag);
+ const isVelocityTag = (tag: string) =>
+ tag.startsWith('velocity/v') || isUnified(tag);
+
+ const paper = releases
+ .filter((r) => isPaperTag(r.tag_name))
+ .map((r) => buildEntry(r, 'paper'));
+ const velocity = releases
+ .filter((r) => isVelocityTag(r.tag_name))
+ .map((r) => buildEntry(r, 'velocity'));
+
+ const all = [...paper, ...velocity];
+ const protocols = await Promise.all(
+ all.map((entry) => fetchProtocolAtTag(entry.tag)),
+ );
+ all.forEach((entry, i) => {
+ entry.protocol = protocols[i];
+ });
+
+ data.value = {
+ paper: paper.filter((e) => e.protocol !== null),
+ velocity: velocity.filter((e) => e.protocol !== null),
+ };
+ } catch {
+ error.value = true;
+ } finally {
+ loading.value = false;
+ }
+ });
+
+ return { data, loading, error };
+}
+
+export type CompatibilityResult = 'compatible' | 'major-mismatch' | 'paper-too-new' | 'velocity-too-new' | 'paper-too-old' | 'velocity-too-old';
+
+export function checkCompatibility(
+ paper: ProtocolVersion,
+ velocity: ProtocolVersion,
+): CompatibilityResult {
+ if (paper.major !== velocity.major) return 'major-mismatch';
+
+ const paperAcceptsVelocity =
+ velocity.minor >= paper.minSupportedMinor && velocity.minor <= paper.minor;
+ const velocityAcceptsPaper =
+ paper.minor >= velocity.minSupportedMinor && paper.minor <= velocity.minor;
+
+ if (paperAcceptsVelocity && velocityAcceptsPaper) return 'compatible';
+ if (!velocityAcceptsPaper && paper.minor > velocity.minor) return 'paper-too-new';
+ if (!paperAcceptsVelocity && velocity.minor > paper.minor) return 'velocity-too-new';
+ if (!velocityAcceptsPaper && paper.minor < velocity.minSupportedMinor) return 'paper-too-old';
+ if (!paperAcceptsVelocity && velocity.minor < paper.minSupportedMinor) return 'velocity-too-old';
+ return 'major-mismatch';
+}
+
+export function isCompatible(
+ paper: ProtocolVersion,
+ velocity: ProtocolVersion,
+): boolean {
+ return checkCompatibility(paper, velocity) === 'compatible';
+}
+
+export function formatProtocol(p: ProtocolVersion): string {
+ return `${p.major}.${p.minor}.${p.patch}`;
+}
diff --git a/website/.vitepress/theme/index.ts b/website/.vitepress/theme/index.ts
index bc82522..75bda89 100644
--- a/website/.vitepress/theme/index.ts
+++ b/website/.vitepress/theme/index.ts
@@ -1,5 +1,6 @@
import type { Theme } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
+import CompatibilityMatrix from './components/CompatibilityMatrix.vue';
import DownloadCard from './components/DownloadCard.vue';
import './custom.css';
@@ -7,5 +8,6 @@ export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('DownloadCard', DownloadCard);
+ app.component('CompatibilityMatrix', CompatibilityMatrix);
},
} satisfies Theme;
diff --git a/website/src/docs/features/velocity.md b/website/src/docs/features/velocity.md
index d1394ec..3fa919e 100644
--- a/website/src/docs/features/velocity.md
+++ b/website/src/docs/features/velocity.md
@@ -6,6 +6,10 @@ layout: doc
Velocity プロキシを経由して複数の Paper / Folia サーバー間でグローバルチャットをリレーします.
+::: tip 互換性について
+Paper プラグインと Velocity プラグインは独立にバージョン管理されています.**両方の最新版を使えば常に動作します.** 古いバージョンを混在させたい場合は,[Paper / Velocity 互換性](/docs/reference/compatibility) を参照してください.
+:::
+
## セットアップ
### 1. Velocity プラグインの導入
@@ -47,45 +51,10 @@ features:
各メッセージに一意な ID が付与され,キャッシュにより同じメッセージが重複して表示されることを防ぎます.キャッシュサイズは `messageDeduplicationCacheSize` (デフォルト: `100`) で設定できます.
-## プロトコルバージョン
-
-Paper と Velocity 間の互換性はプロトコルバージョンで管理されます.接続時にハンドシェイクが行われ,互換性のないバージョン同士では接続が拒否されます.
-
-### バージョンバンプルール
-
-| レベル | 変更例 | 互換性 | デプロイ順序 |
-|--------|--------|--------|-------------|
-| PATCH (1.0.0 → 1.0.1) | optional フィールド追加,新 sub-channel 追加 | 完全互換 (`ignoreUnknownKeys=true` で安全) | 順不同,いつでも |
-| MINOR (1.0.x → 1.1.0) | required フィールド追加,既存 sub-channel のセマンティクス変更 | `MIN_SUPPORTED_MINOR` の範囲内で後方互換 | **Velocity を先に更新** → 各 Paper を順次更新 |
-| MAJOR (1.x.x → 2.0.0) | ワイヤフォーマット変更,sub-channel 削除/リネーム | 非互換 | **全サーバー同時デプロイ** |
-
-### 互換性判定
-
-ハンドシェイク時に以下のルールで互換性が判定されます:
-
-- **MAJOR** が一致すること
-- リモートの **MINOR** が `MIN_SUPPORTED_MINOR` 以上かつ自身の MINOR 以下であること
-- **PATCH** は互換性判定に影響しない
-
-#### 例: Velocity がプロトコル 1.2.0 で `MIN_SUPPORTED_MINOR=1` の場合
-
-| Paper プロトコル | 結果 |
-|-----------------|------|
-| 1.1.x | 接続 OK |
-| 1.2.x | 接続 OK |
-| 1.0.x | 拒否 (`MIN_SUPPORTED_MINOR` より古い) |
-| 1.3.x | 拒否 (Velocity より新しい) |
-| 2.0.x | 拒否 (MAJOR 不一致) |
-
-### 運用サイクル
-
-1. **プロトコル変更なし** → Paper / Velocity を独立にデプロイ可能
-2. **PATCH 変更** → どちら側からでも自由にデプロイ
-3. **MINOR 変更** → Velocity を先行更新し,`MIN_SUPPORTED_MINOR` で旧 Paper の猶予期間を設定.全 Paper 更新後に `MIN_SUPPORTED_MINOR` を引き上げ
-4. **MAJOR 変更** → メンテナンスウィンドウで一括更新
-
## 接続状態
+`/lcv status` で確認できる状態と,それぞれの意味は以下の通りです.
+
| 状態 | 説明 |
|------|------|
| `DISCONNECTED` | 未接続 |
@@ -93,7 +62,13 @@ Paper と Velocity 間の互換性はプロトコルバージョンで管理さ
| `CONNECTED` | 接続済み |
| `FAILED` | 接続失敗 |
-ハンドシェイクのタイムアウトは5秒です.タイムアウトした場合,状態は `FAILED` になります.
+ハンドシェイクのタイムアウトは 5 秒です.タイムアウトした場合,状態は `FAILED` になります.
+
+### `FAILED` になったときの確認ポイント
+
+- Velocity プラグインが正しく導入され,プロキシが起動しているか
+- Paper の `serverName` が Velocity 設定のサーバー名と一致しているか
+- Paper / Velocity プラグインの**プロトコルバージョン**が互換であるか — [互換性マトリクス](/docs/reference/compatibility#互換性マトリクス) で確認できます
## 設定一覧
@@ -107,3 +82,7 @@ Paper と Velocity 間の互換性はプロトコルバージョンで管理さ
## メッセージフォーマット
クロスサーバーチャットの表示形式は `config.yml` の `messageFormat.crossServerGlobalChatFormat` でカスタマイズできます.詳細は[メッセージフォーマット](/docs/reference/message-format)を参照してください.
+
+## 関連ドキュメント
+
+- [Paper / Velocity 互換性](/docs/reference/compatibility) — プロトコルバージョンとローリングアップデートの詳細
diff --git a/website/src/docs/reference/compatibility.md b/website/src/docs/reference/compatibility.md
new file mode 100644
index 0000000..dc0c3b2
--- /dev/null
+++ b/website/src/docs/reference/compatibility.md
@@ -0,0 +1,57 @@
+---
+layout: doc
+---
+
+# Paper / Velocity 互換性
+
+LunaticChat の Paper プラグインと Velocity プラグインは独立にバージョン管理されています.それぞれの組み合わせが動作するかどうかは,両プラグインに埋め込まれた**プロトコルバージョン**で判定されます.
+
+## 結論から
+
+- **両プラグインの最新版同士は常に互換性があります.** 迷ったら両方を最新にしてください.
+- 古いバージョンを混在させたい場合は,下のマトリクスで組み合わせを確認してください.
+- 接続状態は Minecraft サーバーで `/lcv status` を実行すると確認できます.
+
+## 互換性マトリクス
+
+各セルは「その Paper × Velocity の組み合わせが接続できるか」を示します.データは GitHub Releases から自動取得されます.
+
+<CompatibilityMatrix />
+
+## プロトコルバージョンとは
+
+Paper / Velocity 間の通信は LunaticChat 独自のプラグインメッセージプロトコルで行われています.プロトコルにはセマンティックバージョニング (`MAJOR.MINOR.PATCH`) が振られており,接続時のハンドシェイクで両者のバージョンが照合されます.
+
+判定ルールは以下です:
+
+- **MAJOR** が一致すること
+- 相手の **MINOR** が自分の `MIN_SUPPORTED_MINOR` 以上かつ自分の `MINOR` 以下であること
+- **PATCH** は判定に影響しない
+
+`MIN_SUPPORTED_MINOR` は「どこまで古い MINOR を受け入れるか」を示す値で,ローリングアップデート中の猶予期間を作るために使われます.
+
+### バージョンバンプの基準
+
+| レベル | 変更例 | 互換性 | デプロイ順序 |
+|--------|--------|--------|-------------|
+| **PATCH** (1.0.0 → 1.0.1) | optional フィールド追加,新 sub-channel 追加 | 完全互換 (`ignoreUnknownKeys=true` で安全) | 順不同,いつでも |
+| **MINOR** (1.0.x → 1.1.0) | required フィールド追加,既存 sub-channel のセマンティクス変更 | `MIN_SUPPORTED_MINOR` の範囲内で後方互換 | **Velocity を先に更新** → 各 Paper を順次更新 |
+| **MAJOR** (1.x.x → 2.0.0) | ワイヤフォーマット変更,sub-channel 削除/リネーム | 非互換 | **全サーバー同時デプロイ** |
+
+### ローリングアップデートの考え方
+
+1. **プロトコル変更なし**:Paper / Velocity を独立にデプロイ可能.プラグインのバグ修正やリファクタはここに入ります.
+2. **PATCH 変更**:どちら側からでも自由にデプロイ可能.
+3. **MINOR 変更**:Velocity を先行更新し,`MIN_SUPPORTED_MINOR` で旧 Paper を許容.全 Paper 更新後に `MIN_SUPPORTED_MINOR` を引き上げ.
+4. **MAJOR 変更**:メンテナンスウィンドウで一括更新.
+
+## ハンドシェイクの挙動
+
+接続時は以下の流れで互換性が確認されます:
+
+1. Paper サーバー起動時に Velocity に対してハンドシェイクを送信
+2. Velocity が自身のプロトコルバージョンと照合
+3. 不一致の場合は接続が拒否され,状態が `FAILED` になる
+4. ハンドシェイクのタイムアウトは 5 秒
+
+接続状態は `/lcv status` で確認できます.詳細は [Velocity 連携](/docs/features/velocity#接続状態) を参照してください.
diff --git a/website/src/en/docs/features/velocity.md b/website/src/en/docs/features/velocity.md
index 9241e80..5715014 100644
--- a/website/src/en/docs/features/velocity.md
+++ b/website/src/en/docs/features/velocity.md
@@ -6,6 +6,10 @@ layout: doc
Relays global chat across multiple Paper / Folia servers via a Velocity proxy.
+::: tip About compatibility
+The Paper and Velocity plugins are versioned independently. **Using the latest of both always works.** If you need to mix older versions, see [Paper / Velocity Compatibility](/en/docs/reference/compatibility).
+:::
+
## Setup
### 1. Install the Velocity Plugin
@@ -47,45 +51,10 @@ 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`).
-## Protocol Version
-
-Compatibility between Paper and Velocity is managed by protocol version. A handshake is performed upon connection, and incompatible versions are rejected.
-
-### Version Bump Rules
-
-| Level | Example Change | Compatibility | Deployment Order |
-|-------|---------------|---------------|-----------------|
-| PATCH (1.0.0 -> 1.0.1) | Adding optional fields, new sub-channels | Fully compatible (safe with `ignoreUnknownKeys=true`) | Any order, anytime |
-| MINOR (1.0.x -> 1.1.0) | Adding required fields, changing existing sub-channel semantics | Backward compatible within `MIN_SUPPORTED_MINOR` range | **Update Velocity first** -> then update each Paper server |
-| MAJOR (1.x.x -> 2.0.0) | Wire format changes, removing/renaming sub-channels | Incompatible | **Simultaneous deployment of all servers** |
-
-### Compatibility Check
-
-Compatibility is determined during the handshake using the following rules:
-
-- **MAJOR** versions must match
-- The remote **MINOR** must be at least `MIN_SUPPORTED_MINOR` and at most the local MINOR
-- **PATCH** does not affect the compatibility check
-
-#### Example: Velocity with protocol 1.2.0 and `MIN_SUPPORTED_MINOR=1`
-
-| Paper Protocol | Result |
-|---------------|--------|
-| 1.1.x | Connection OK |
-| 1.2.x | Connection OK |
-| 1.0.x | Rejected (older than `MIN_SUPPORTED_MINOR`) |
-| 1.3.x | Rejected (newer than Velocity) |
-| 2.0.x | Rejected (MAJOR mismatch) |
-
-### Operational Cycle
-
-1. **No protocol change** -> Paper / Velocity can be deployed independently
-2. **PATCH change** -> Deploy freely from either side
-3. **MINOR change** -> Update Velocity first and set `MIN_SUPPORTED_MINOR` to allow a grace period for older Paper servers. After all Paper servers are updated, raise `MIN_SUPPORTED_MINOR`
-4. **MAJOR change** -> Simultaneous update during a maintenance window
-
## Connection States
+The states reported by `/lcv status` and their meanings:
+
| State | Description |
|-------|-------------|
| `DISCONNECTED` | Not connected |
@@ -95,6 +64,12 @@ Compatibility is determined during the handshake using the following rules:
The handshake timeout is 5 seconds. If the handshake times out, the state becomes `FAILED`.
+### Troubleshooting `FAILED`
+
+- Confirm the Velocity plugin is installed and the proxy is running
+- Confirm the Paper `serverName` matches the server name configured in Velocity
+- Confirm the **protocol versions** of the Paper and Velocity plugins are compatible — see the [compatibility matrix](/en/docs/reference/compatibility#compatibility-matrix)
+
## Configuration Reference
| Setting Key | Default | Description |
@@ -107,3 +82,7 @@ The handshake timeout is 5 seconds. If the handshake times out, the state become
## Message Format
The display format for cross-server chat can be customized via `messageFormat.crossServerGlobalChatFormat` in `config.yml`. See [Message Format](/en/docs/reference/message-format) for details.
+
+## Related Documents
+
+- [Paper / Velocity Compatibility](/en/docs/reference/compatibility) — protocol version and rolling update details
diff --git a/website/src/en/docs/reference/compatibility.md b/website/src/en/docs/reference/compatibility.md
new file mode 100644
index 0000000..a2b1e8f
--- /dev/null
+++ b/website/src/en/docs/reference/compatibility.md
@@ -0,0 +1,57 @@
+---
+layout: doc
+---
+
+# Paper / Velocity Compatibility
+
+The Paper and Velocity plugins of LunaticChat are versioned independently. Whether a given combination works is determined by the **protocol version** embedded in each plugin.
+
+## TL;DR
+
+- **The latest Paper and the latest Velocity are always compatible.** When in doubt, use the latest of both.
+- If you need to mix older versions, check the matrix below.
+- You can verify the live connection state by running `/lcv status` on the Minecraft server.
+
+## Compatibility Matrix
+
+Each cell indicates whether the corresponding Paper × Velocity combination can connect. Data is fetched from GitHub Releases automatically.
+
+<CompatibilityMatrix />
+
+## What Is a Protocol Version?
+
+Paper and Velocity communicate via a LunaticChat-specific plugin messaging protocol. The protocol carries a semantic version (`MAJOR.MINOR.PATCH`), and a handshake at connection time validates both sides.
+
+The rules are:
+
+- **MAJOR** must match exactly
+- The remote **MINOR** must be at least `MIN_SUPPORTED_MINOR` and at most the local `MINOR`
+- **PATCH** does not affect compatibility
+
+`MIN_SUPPORTED_MINOR` controls how far back the local plugin accepts older peers, providing a grace window during rolling updates.
+
+### Version Bump Rules
+
+| Level | Example Change | Compatibility | Deployment Order |
+|-------|---------------|---------------|------------------|
+| **PATCH** (1.0.0 → 1.0.1) | Adding optional fields, new sub-channels | Fully compatible (safe with `ignoreUnknownKeys=true`) | Any order, anytime |
+| **MINOR** (1.0.x → 1.1.0) | Adding required fields, changing existing sub-channel semantics | Backward compatible within `MIN_SUPPORTED_MINOR` range | **Update Velocity first** → then update each Paper server |
+| **MAJOR** (1.x.x → 2.0.0) | Wire format changes, removing/renaming sub-channels | Incompatible | **Simultaneous deployment of all servers** |
+
+### Rolling Update Strategy
+
+1. **No protocol change**: Paper and Velocity can be deployed independently. Plugin bug fixes and refactors fall here.
+2. **PATCH change**: Deploy from either side freely.
+3. **MINOR change**: Update Velocity first and use `MIN_SUPPORTED_MINOR` as a grace window for older Paper servers. After all Paper servers are updated, bump `MIN_SUPPORTED_MINOR`.
+4. **MAJOR change**: Update all servers simultaneously during a maintenance window.
+
+## Handshake Behavior
+
+Compatibility is checked at connection time:
+
+1. The Paper server sends a handshake to Velocity at startup
+2. Velocity validates the protocol version against its own
+3. On mismatch, the connection is rejected and the state becomes `FAILED`
+4. The handshake timeout is 5 seconds
+
+Live connection state is available via `/lcv status`. See [Velocity Integration](/en/docs/features/velocity#connection-states) for details.
diff --git a/website/src/en/index.md b/website/src/en/index.md
index b823a0d..766478d 100644
--- a/website/src/en/index.md
+++ b/website/src/en/index.md
@@ -123,7 +123,18 @@ features:
<hr class="home-divider" />
-<!-- Section 5: Platforms -->
+<!-- Section 5: Compatibility Matrix -->
+<div class="platform-section">
+ <h2>Paper / Velocity Compatibility</h2>
+ <p class="section-desc">Latest versions are always compatible. Mix older versions? Check the matrix below.</p>
+ <div style="text-align: left;">
+ <CompatibilityMatrix />
+ </div>
+</div>
+
+<hr class="home-divider" />
+
+<!-- Section 6: Platforms -->
<div class="platform-section">
<h2>Multi-Platform Support</h2>
<p class="section-desc">Flexibly deploy to match your server setup</p>
diff --git a/website/src/index.md b/website/src/index.md
index 007a6ad..c1d867d 100644
--- a/website/src/index.md
+++ b/website/src/index.md
@@ -123,7 +123,18 @@ features:
<hr class="home-divider" />
-<!-- Section 5: プラットフォーム -->
+<!-- Section 5: 互換性マトリクス -->
+<div class="platform-section">
+ <h2>Paper / Velocity 互換性</h2>
+ <p class="section-desc">最新版同士は常に互換.古いバージョンを混在させる場合は下のマトリクスで確認できます</p>
+ <div style="text-align: left;">
+ <CompatibilityMatrix />
+ </div>
+</div>
+
+<hr class="home-divider" />
+
+<!-- Section 6: プラットフォーム -->
<div class="platform-section">
<h2>マルチプラットフォーム対応</h2>
<p class="section-desc">サーバーの構成に合わせて柔軟に導入できます</p>