Complete local voxel MVP with server, client, MCP, packages and world interchange

This commit is contained in:
Emil
2026-09-14 17:35:17 +03:00
parent cf8d1aae2e
commit b6ba064339
98 changed files with 58116 additions and 194 deletions
+165
View File
@@ -0,0 +1,165 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="color-scheme" content="dark" />
<title>Shacraft · Мир</title>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 32 32%27%3E%3Cpath fill=%27%2394ae81%27 d=%27M16 1 30 9v15L16 32 2 24V9z%27/%3E%3C/svg%3E"
/>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<canvas
id="world-canvas"
aria-label="Трёхмерный мир Shacraft. WASD — движение, пробел — прыжок, мышь — обзор."
></canvas>
<div id="labels" aria-hidden="true"></div>
<header class="topbar">
<div class="identity">
<svg viewBox="0 0 28 32" width="27" height="30" aria-hidden="true">
<path d="M14 1 27 8.5 14 16 1 8.5Z" fill="#c0d3a9" />
<path d="M1 8.5 14 16v15L1 23.5Z" fill="#6d8560" />
<path d="m14 16 13-7.5v15L14 31Z" fill="#94ae81" />
</svg>
<div>
<strong>Shacraft</strong
><button id="world-button" class="world-name" title="Выбрать мир">
Подключение… ▾
</button>
</div>
<span id="connection" role="status">Загрузка</span>
</div>
<nav aria-label="Игровое меню">
<button id="library-button">Блоки <kbd>E</kbd></button
><button id="diagnostics-button" title="Открыть диагностику">
Диагностика <kbd>F3</kbd></button
><button id="menu-button" aria-label="Меню игры">☰</button>
</nav>
</header>
<div id="crosshair" aria-hidden="true"></div>
<div id="target-label"></div>
<div id="match" hidden></div>
<div id="toast" role="status" hidden></div>
<div class="bottom-left">
<div id="chat-log" aria-live="polite"></div>
<form id="chat-form" hidden>
<input
id="chat-input"
autocomplete="off"
maxlength="240"
aria-label="Сообщение в чат"
placeholder="Сообщение · Enter — отправить, Esc — закрыть"
/>
</form>
<div id="hints">
<span><kbd>W A S D</kbd> движение</span
><span><kbd>Пробел</kbd> прыжок</span
><span><kbd>ЛКМ / ПКМ</kbd> убрать / поставить</span
><span><kbd>T</kbd> чат</span>
</div>
</div>
<footer>
<div id="selected-name"></div>
<div id="hotbar" role="toolbar" aria-label="Быстрый выбор блоков"></div>
<div id="play-hint">
Нажмите на мир для управления · без захвата мыши: удерживайте и двигайте
</div>
</footer>
<div id="fatal" hidden role="alert"></div>
<aside id="menu" class="panel" hidden aria-label="Меню игры">
<div class="panel-head">
<h1>Меню игры</h1>
<button data-close="menu" aria-label="Закрыть меню">×</button>
</div>
<label for="player-name">Имя игрока</label>
<div class="inline">
<input id="player-name" maxlength="24" autocomplete="off" /><button
id="rename-button"
>
Применить
</button>
</div>
<button id="resume-button" class="primary wide">Вернуться в мир</button
><button id="respawn-button" class="wide">
Вернуться к точке появления</button
><button id="spleef-button" class="wide">Начать Spleef</button>
<p class="muted">
В Spleef убирайте снег под соперником. Последний игрок на арене
побеждает.
</p>
<div class="rule"></div>
<label for="sensitivity">Чувствительность мыши</label
><input
type="range"
id="sensitivity"
min="0.0008"
max="0.006"
step="0.0002"
value="0.0024"
/>
<p class="muted">
Esc освобождает мышь. Перетаскивание работает и без захвата. Средняя
кнопка выбирает блок под прицелом.
</p>
<button id="reconnect-button" class="wide">Переподключиться</button>
</aside>
<aside id="worlds" class="panel" hidden aria-label="Выбор мира">
<div class="panel-head">
<h1>Миры</h1>
<button data-close="worlds" aria-label="Закрыть миры">×</button>
</div>
<p class="muted">Изменения каждого экземпляра сохраняются независимо.</p>
<div id="world-list"></div>
</aside>
<aside
id="library"
class="panel library"
hidden
aria-label="Каталог блоков"
>
<div class="panel-head">
<div>
<h1>Библиотека блоков</h1>
<p id="catalog-count" class="muted">Каталог сервера</p>
</div>
<button data-close="library" aria-label="Закрыть библиотеку">×</button>
</div>
<input
id="catalog-search"
type="search"
placeholder="Поиск: oak, stairs, glass…"
aria-label="Поиск по каталогу"
/>
<p class="muted">
Выберите блок для текущей ячейки. Свойства состояний показаны под
названием.
</p>
<div id="catalog-list"></div>
<button id="catalog-more" class="wide">Показать ещё</button>
</aside>
<aside
id="diagnostics"
class="panel diagnostics"
hidden
aria-label="Диагностика"
>
<div class="panel-head">
<h1>Диагностика</h1>
<button data-close="diagnostics" aria-label="Закрыть диагностику">
×
</button>
</div>
<dl id="debug-values"></dl>
<div id="package-status" class="muted">Пакеты ещё не загружены</div>
<button id="resync-button" class="wide">Сверить снимок мира</button>
<p class="muted">
Сервер подтверждает движение и правки. RAM процесса и полезные данные
кэша — разные измерения.
</p>
</aside>
<script type="module" src="/app.js"></script>
</body>
</html>