# Фич-матрица Очередь **M7**: генерация мира, редстоун и остальная ванила. Внутри раздела сверху вниз. Строки со статусом «готово» — граница, их в работу не берут. M7 берёт «нет» и «частично». Протокол, чанки, Anvil, игрок, инвентарь, крафт 2×2 и верстак, печи (`smelting`, `blasting`, `smoking`), чары, жидкости, лук и арбалет, стрелы и сферы опыта описаны в [milestones.md](milestones.md) и сюда построчно не копируются. Здесь только дыры внутри этих областей и то, чего там нет. M6 и M8 в эту матрицу не входят. Память сервера: `memory-profile=auto|low|normal` выбирает потоки и лимиты кэшей; `low` проверен на полёте 20 блоков/с с обзором 10 (средний обзор 99.2 %). Индексы секций до 16 состояний занимают 2 КиБ, неизменяемые таблицы состояний генератора разделяются между измерениями, кэши чистятся при простое. Замеры — в [perf.md](perf.md). Метки декорации и наборы сущностей пока не ограничены: они сохраняют однократность генерации и заселения. Статусы: - **готово** — заявленное поведение уже играет. - **частично** — игрок уже может этим пользоваться; в заметке сказано, чего нет. - **нет** — имени блока в `is_source`, чтения свойства `power`, коллизии поршня или звука установки недостаточно. Это заготовка данных, не механика. Строка — семейство механик, не вид дерева и не вид моба. ## Генерация и измерения Плоский мир собирается `ChunkColumn::flat`. `level-type=minecraft:normal` заполняет недостающий чанк шумом оверворлда (камень, воздух, вода, лава), расставляет биомы по климату и наносит поверхностный слой (трава, земля, песок, снег, deepslate, руды меди и железа). `large_biomes` и `amplified` берут свои `noise_settings`, `single_biome_surface` держит один биом, плоский `the_void` с фичами ставит стартовую платформу. Карверы вырезают пещеры и каньоны, фичи расставляют руды, деревья и растительность. Колодец, окаменелости и серный источник ставятся из `.nbt`. Деревни, пиллагерские аванпосты и брошенные лагеря собираются из пулов. Пустынная пирамида и храм в джунглях — один кусок. Шахт нет. | Фича | Статус | Этап | Заметка | |---|---|---|---| | Плоский мир | готово | M2/M3 | `ChunkColumn::flat`, один оверворлд | | Сид из `level-seed` | готово | M7 | Число или `String.hashCode`, пусто — случайный; хранится в `level.dat` (`WorldGenSettings.seed`), клиенту — `hashed_seed` (SHA-256). Плагинам `World.getSeed` пока не отдаётся | | Тип мира | готово | M7 | `level-type`: `flat` (JSON слоёв или пресет `flat_level_generator_preset`, флаг `features` в `level.dat`), `normal`, `large_biomes`, `amplified` — шум из `world_preset` (`noise_settings` и `biome_source` `multi_noise` / `overworld`). `single_biome_surface` — настройки оверворлда и фиксированный биом (в пресете plains; JSON `generator-settings` с полем `biome` его заменяет). В `level.dat` тип `minecraft:noise` и `biome_source`. Плоский `the_void` с `features` ставит платформу 33×33 (`void_start_platform`: камень, булыжник в центре) и спавн на ней. Уже записанный мир из `level.dat` не переписывается. `debug_all_block_states`, Ад и Край этим не создаются | | Рельеф оверворлда | готово | M7 | Шум `final_density` и акифер: камень, воздух, вода и лава совпадают с ванилью и подставляются, если чанка нет на диске. Сверка с `server.jar` через region-файлы: расхождения только там, где ванила прогоняет карверы и фичи | | Биомы | готово | M7 | `MultiNoiseBiomeSource` по шести осям климата (`temperature`, `vegetation`, `continents`, `erosion`, `depth`, `ridges`) из `noise_settings`: 7594 точки `ParameterPoint` выгружает `ChunkwakeBiomes` в `reports/biomes.csv`. Биом на секцию — палитра 4×4×4 в сети и в Anvil, как у ванили. Сверка с ванилью: 24576 клеток, расхождений нет. Поиск биома — перенос `Climate.RTree` (19 детей на узел, та же сортировка и нарезка): ничьи по штрафу решает `lastResult` — ответ предыдущего запроса, поэтому клетки обходятся в порядке `LevelChunkSection.fillBiomesFromNoise` (x, y, z). Без этого столбец, где `frozen_peaks` и `snowy_slopes` равны, уходил не в тот биом (92 клетки у древнего города) | | Поверхностный слой | готово | M7 | Все правила `material_rule` (`block`, `sequence`, `condition`, `biome`, `noise_threshold`, `y_above`, `not`, `hole`, `steep` (`gradientX <= -4 || gradientZ >= 4`, как в `SteepCondition` 26.3), `vertical_gradient`, `ore_vein`, `bandlands`, `above_preliminary_surface`) и все условия `material_condition`. Трава, земля, песок, гравий, глина, снег, лёд, deepslate ниже нуля, руды меди и железа. Стенд `vanilla_regions` запускает настоящий `server.jar` и сравнивает `.mca`: биомы с «дрожанием» границ (`BiomeManager`), 0 расхождений по клеткам биомов на 64 чанка; оставшиеся расхождения блоков — фичи (руды, растения, айсберги). `hashed_seed` для клиента теперь little-endian, как у ванили. Условие `biome` берёт биом на высоте самого блока (`Context.updateY` через `BiomeManager` с дрожанием), а не клетки 4×4×4: окно древнего города 13959 → 12019 расхождений | | Пещеры и водоносные слои | готово | M7 | Пещеры из `final_density` (noodle, cheese, spaghetti, pillars) и карверы `cave`, `cave_extra_underground`, `canyon`: соседи в радиусе 8 чанков, `setLargeFeatureSeed`, `CarvingMask`, заливка акифером, `topMaterial` под снятой травой. Сверка с ванилью: 0 расхождений по камню, воздуху и жидкостям, кроме мест фич и структур. Жидкость «потечёт после генерации», как у ванили: флаг `shouldScheduleFluidUpdate` акифера (стыки разных уровней, вода над глобальной лавой) метит клетки заливки и карверов, родники и трещины жеод ставят тик жидкости; сервер планирует эти тики, когда чанк сгенерирован. Стенд `examples/postproc_check` сверяет метки с `PostProcessing` прото-чанков `server.jar` в статусе `terrain`: сид 12345 — 5463 общих, лишних у нас 0; древний город — 1745 и 0 (у ванили сверх этого метки кусков структур и фич соседей). Не перенесено: тики блоков от `lake` и `simple_block`, метки `MaterialSystem` и кусков структур. Озеро метит до двух блоков над своим воздухом для пересчёта формы (`markAboveForPostProcessing`) | | Руды, деревья, растительность | готово | M7 | Каркас декорации `applyBiomeDecoration`: `placed_feature` с модификаторами `count`, `in_square`, `height_range`, `biome`, `rarity_filter`, `heightmap`, `fixed_placement`, `count_on_every_layer`, `cuboid`, порядок шагов `FeatureSorter`, сиды `setDecorationSeed`/`setFeatureSeed` на Xoroshiro, чанк отдаётся после декорации всех 9 соседей. Руды `ore` и `scattered_ore` (уголь, железо, медь, золото, алмазы, красный камень, лазурит, изумруды, туф, гранит, диорит, андезит, гравий, земля) и жилы `ore_vein` совпадают с ванилью бит в бит. Деревья `tree` со всеми стволами, кронами, корнями мангров, декораторами и пересчётом `distance` листвы, поваленные стволы `fallen_tree`, гигантские грибы, трава, цветы, тростник, кактусы, ламинария, водоросли, растительность пышных пещер (`vegetation_patch`, `block_column`), селекторы и модификаторы `environment_scan`, `noise_threshold_count`, `block_predicate_filter`: деревья на сидах 12345 и 1 совпадают с ванилью. Также перенесены озёра, данжи, жеоды, сталактиты, родники, диски, лианы, лишайник, айсберги, кораллы, бамбук, ледяные шипы, снег и лёд (`freeze_top_layer`), магма под дном, скалк (`SculkPatchFeature` и `SculkSpreader` целиком: курсоры, заряд, распад, жилы и рост; катализатор и визгун — из обёртки `sequence`), `sequence` (останавливается на первой неудачной фиче, как `SequenceFeature`), `overlay`, цепочки `simple_random_selector`, `clamped_normal` в `IntProvider`. Перенесены все 171 фичи, включая `desert_well`, `rooted_sulfur_spring`, `fossil_upper` и `fossil_lower`: шаблоны `.nbt` ставятся с поворотом, `block_rot`, `protected_blocks` и `rule`. Лут археологии пишется: `append_loot` кладёт `LootTable` и `LootTableSeed` в `brushable_block`, семя — из `RandomSource.create(Mth.getSeed(pos))` того блока. На сиде 12345 сверка с `server.jar` в чанках фичи и соседях: `suspicious_sand` и `sandstone_slab` колодца, `bone_block` окаменелости (вместе с осью) и `tuff` с `magma_block` серного источника совпадают. Покрытие держит тест `overworld_features_parse`: без `CHUNKWAKE_DATA` он пропускается, с ним падает на любом необъявленном отказе. Стоимость: прото-чанк ~13.8 мс, декорация ~19.6 мс на поток; порядок декорации обязан совпадать с порядком запросов, поэтому область под одним замком и потоки масштабируют счёт лишь частично (x2.2 на 4, x4.4 на 32) | | Структуры оверворлда | частично | M7 | Деревни пяти типов, пиллагерский аванпост и 18 брошенных лагерей: `random_spread` (соль, `legacy_type_1`, зона исключения, тег биома), пулы и `jigsaw` с `use_expansion_hack`, высота `WORLD_SURFACE_WG`, борода `beard_thin`, процессоры `rule` (`random_block_match`) и `block_rot`. Куски пишутся в начале шага `surface_structures`. Лут пишется так же, как в ванили: `LootTable` из NBT шаблона плюс `LootTableSeed` из `random.nextLong()` на каждый `RandomizableContainer` (`chest`, `barrel`, `copper_chest`); готовые `Items` переносятся как есть, мобы не пишутся. Сид 12345 против `server.jar`: ратуша, улицы (`dirt_path`), башня аванпоста и палатка лесного лагеря (`campfire`, `straw_bed`, `white_wool_stairs`) совпадают. Пустынная пирамида и храм в джунглях — `SinglePieceStructure` на той же сетке `random_spread` без частоты и без зоны исключения: биом середины чанка и поверхности, четыре угла следа по `WORLD_SURFACE_WG` не ниже моря, поворот через `setLargeFeatureSeed`. Высота сдвигается при записи по живой `MOTION_BLOCKING_NO_LEAVES` — у пустыни минимум и `-nextInt(3)`, у джунглей целое среднее со сдвигом 0. Сундуки, раздатчики и `suspicious_sand` получают `LootTable` и `LootTableSeed`, как в ванили. На сиде 12345 песчаник, резьба, лестницы, терракота и `suspicious_sand` пирамиды и булыжник с мхом храма совпадают с `server.jar`. Хижина — кусок 7×7×9, среднее земли со сдвигом 0. Иглу — шаблоны верха, шахты и лаборатории, подвал с шансом 0.5, высота входа минус 91, снег на люке. Кораблекрушение — один шаблон берега или океана, высота заморожена, восемь палитр, воздух не затирает воду. Ведьма, кошка и житель не пишутся; лут — пишется: у иглу `igloo_chest`, у корабля `shipwreck_map`, `shipwreck_treasure` и `shipwreck_supply` через `setBlockEntityLootTable` на клетку ниже маркера. На сиде 12345 котёл, еловые лестницы и забор хижины, снег иглу и обшивка корабля совпадают с `server.jar`. Шахты `mineshaft` и `mineshaft_mesa`: `legacy_type_3`, шаг `underground_structures`, комната, коридор, лестница и перекрёсток. Вагонетка с сундуком — сущность, поэтому лут шахты не пишется; пещерный паук тоже сущность. На сиде 12345 доски, заборы, рельсы, паутина, спаунер, цепи и факелы обычной шахты совпадают с `server.jar`; опора над доской ищет потолок через `canSupportCenter(DOWN)` (по коробкам `shapes.csv`), поэтому столб забора держит цепь и лишних `oak_log` под опорой нет. Океанский монумент: `spread_type` triangular, соль 10387313, шаг `surface_structures`. Стражи не пишутся. На сиде 12345 призмарин, кирпичи, тёмный призмарин, морские фонари, золото и мокрая губка совпадают с `server.jar`. Крепость: `concentric_rings` (128 точек, соль 0), шаг `surface_structures`, адаптация `bury`. Кольца считаются `Legacy` от сида мира, куски — `WorldgenRandom` над `Legacy`. На сиде 12345 ближайшая к началу координат крепость — чанк (−35, −103), тот же, что `/locate` у `server.jar` (`[-560, ~, -1648]`). Дерево из 225 кусков совпадает с NBT старта: тип, коробка, поворот и глубина каждого куска, включая комнату портала. В окне вокруг старта совпадают рамки портала вместе с глазом, двери, пустые сундуки, блок спаунера, лестницы, плиты, решётки и книжные полки. Расходятся варианты кирпича (`cracked`, `mossy`, `infested`) в одном чанке, где у нас уже разошёлся рельеф. Мобы не пишутся, лут пишется: таблицы `stronghold_corridor`, `stronghold_crossing`, `stronghold_library` и `simple_dungeon` (у данжей внутри крепости). Сверка блок-сущностей на сиде 12345 (`CHUNKWAKE_BELOOT=1`): 11 контейнеров, расходятся 1 — семя сундука библиотеки, чей кусок делит чанк с разошедшимся рельефом. Лесной особняк: `random_spread`, соль 10387319, spacing 80, `spread_type` triangular, шаг `surface_structures`. Сетка-лабиринт `MansionGrid` (11×11, три этажа, комнаты 1x1/1x2/2x2 и потайные) и размещение кусков с зеркалом. Старт — биом в столбце центра чанка, поворот через `setLargeFeatureSeed`, высота по четырём углам 5×5 не ниже 60. После установки под низ коробки кладётся булыжник. Ведьма и жители не пишутся; лут пишется: 72 сундука шаблонов переносят готовые `Items`, маркеры `Chest*` дают `woodland_mansion` с семенем `nextLong`. На сиде 12345 особняк — чанк (291, 441), тот же, что `/locate` у `server.jar` (`[4656, ~, 7056]`). Все 487 кусков совпадают с NBT старта: имя шаблона, коробка, зеркало и поворот. В окне вокруг старта доски, лестницы, плиты, брёвна, книжные полки, сундуки, решётки, лестницы-стремянки, факелы, кровати, ковры, грядки и двери совпадают. Расходятся соединения `cobblestone_wall` (у нас `tall` там, где у ванили `low`) и шесть `moss_carpet` от фона. Руины портала: `random_spread`, соль 34222645, spacing 40, семь видов по биомам, шаг `surface_structures`. Выбор набора через `nextFloat`, шаблон из `ruined_portal/*` (гигантский с шансом 0.05), поворот и зеркало, высота `findSuitableY` с отсевом по трём нижним углам. Процессоры `block_age`, `lava_submerged_block`, `rule` (золото в воздух, лава в магму или незеррак) и `blackstone_replace`. После установки — пятно незеррака, капельные столбы, лианы и листва. На сиде 12345 портал в чанке (1, 0), как у `/locate` у `server.jar` (`[16, ~, 0]`); обсидиан, плачущий обсидиан, булыжник, золото, решётки, пятно незеррака, `magma_block` и пузырьковые столбы над ней совпадают. `Properties.cold` — `can_be_cold` и `coldEnoughToSnow` шумового биома в `origin` (ванильный `getTemperature`: модификатор `frozen`, охлаждение выше `sea_level + 17` по `TEMPERATURE_NOISE`); холодный портал не бросает `nextFloat` на магму, и без этого расходилось всё пятно. Океанские руины `ocean_ruin_cold` и `ocean_ruin_warm`: `random_spread` соль 14357621, spacing 20, шаг `surface_structures`. Доля крупной руины 0.3, грозди 0.9. Тёплая — один шаблон, холодная — три слоя (кирпич, трещины, мох) с целостностью 0.9/0.7/0.5. Высота считается при постановке по живому рельефу (`OceanRuinPiece.getHeight`). До пяти песка или гравия становятся подозрительными через `capped` + `append_loot`, сундук заливается водой по `waterlogged`, утопленник не пишется, но его клетка становится водой или воздухом. На сиде 12345 холодная руина — чанк (7, 5), как у `/locate` у `server.jar` (`[112, ~, 80]`); кирпичи, трещины, мох и гравий совпадают, расходятся пять клеток на границе чанка; подозрительный гравий (`capped` + `append_loot`) даёт 10 из 10 позиций ванили плюс 3 лишние: у нас остаётся лишний кандидат на перемешивание. Старты одной структуры в чанке обходятся как в ванили: ссылка есть только у старта в ±8 чанках, чья коробка (с запасом 12 у адаптации рельефа) задевает чанк, а порядок — обход fastutil `LongOpenHashSet` ссылок, и генератор декорации идёт по ним в этом порядке. Пузырьковые столбы над `magma_block` и `soul_sand` достраиваются при выдаче чанка (`postProcessGeneration` → `BubbleColumnBlock.updateColumn`). Итог сверки 64 чанков с `server.jar`: сид 12345 — 8677 расхождений при 16543 между двумя прогонами самой ванили; сверх этого шума остаётся 535, сид 1 — 840. Почти всё это тики сервера, которых генерация не делает: протёкшие из источников вода и лава (и камень там, где лава легла на воду), подросшая ламинария. Ещё два вида расхождений: наложение рудных пятен гранита и андезита на краю окна, где результат задаёт порядок декорации соседних чанков, и коридор шахты в западном столбце окна сида 1 (~55 клеток) Камеры испытаний: `random_spread` соль 94251327, spacing 34, шаг `underground_structures`, `start_height` uniform −40..−20, адаптация `encapsulate`, `dimension_padding` 10, `use_expansion_hack` выключен, `liquid_settings` `ignore_waterlogging`. Дерево кусков сходится с `server.jar` бит в бит: все 274 куска на сиде 12345 совпадают по порядку, коробке и повороту (тест `trial_matches_vanilla_pieces`). Ключ к этому — `pool_aliases`: `direct` фиксирован, `random` и `random_group` дают ОДИН взвешенный выбор на точку старта через `forkPositional().at(startPos)`, а цели псевдонимов грузятся явно, потому что из пулов на них не ссылаются. В окне 16 чанков остаётся 1088 расхождений, и все они — фон и декор вокруг (кельп, ламинария, морская трава, грибы, глина, мшистый камень): `tuff_bricks`, `chiseled_tuff_bricks`, `polished_tuff` и медные блоки совпадают. Спаунеры и хранилища пишутся блоками. Лут контейнеров пишется: у 57 сундуков, бочек и раздатчиков в 16 чанках `LootTable` и `LootTableSeed` совпадают (`CHUNKWAKE_BELOOT=1`), мобы не пишутся. Древний город: jigsaw `ancient_city/city_center` с `start_jigsaw_name` (`getRandomNamedJigsaw`: старт сдвигается так, чтобы названный разъём встал в точку, высота без проекции), `start_height` −27, `beard_box`, `dimension_padding`, шаг `underground_structures`. Структуры ставятся на своём шаге декорации, а не только на 3 и 4: без этого не было ни древних городов, ни крепостей Ада. Окно 64 чанка у (98, 52) на сиде −6843522070344381082: 0 клеток биомов, 13959 расхождений блоков при 24513 между двумя прогонами самой ванили (forceload по x и по z). Остаток того же рода, что и шум ванили: скалк и его жилы, вишни и лепестки, рудные пятна на стыках чанков | | Нижний мир: измерение | готово | M7 | В `level.dat` три измерения: `minecraft:overworld`, `minecraft:the_nether`, `minecraft:the_end` — при любом `level-type`, как в `world_preset`. `Dimension` и `DimState` в [crates/net/src/play.rs](../crates/net/src/play.rs): у каждого измерения свои чанки, шаблон, `min_y`, генератор, точки интереса и кадры. Измерение работы берётся по цепочке: переопределение потока (`with_dim` — генерация чанка, свет, `set_block_in`), измерение сессии игрока (`task_local` `SESSION_DIM`), общее измерение тика. Поэтому два игрока в разных мирах получают каждый свои чанки, блоки и сущности: раньше `set_block_in` на миг переключал общее измерение, и поток чанков игрока в оверворлде собирал чанк Ада с 16 секциями — клиент падал с ошибкой сетевого протокола. Сохранение пишет чанки всех `DimStore`, игрок сохраняет `Dimension` и входит в своё измерение. Тик мира идёт по каждому измерению отдельно (`ticking_dims`: оверворлд всегда, остальные — когда там есть игрок или `/forceload`), рассылки тика помечены измерением, и игрок в другом мире их не получает. При смене измерения инвентарь, выбранная ячейка, здоровье, опыт и эффекты приходят заново (`PlayerList.sendAllPlayerInfo`). `/execute in <мир> run tp` переносит игрока в тот мир | | Генерация Нижнего мира | готово | M7 | `noise_settings/nether` целиком: `min_y` 0, `height` 128, `sea_level` 32, `netherrack` и лава, акиферов нет, `legacy_random_source` (позиционная фабрика `LegacyPositionalRandomFactory`, `fromHashOf` по `String.hashCode`, `old_blended_noise` от `LegacyRandomSource(seed)`), климат через `NormalNoise.createForLegacyNetherBiome` (октавы — от сырых `amplitude_modifiers` и `base_amplitude`, не от нормированных амплитуд), пять биомов пресета. Карвер `nether_cave`, все 37 фич биомов Ада (`delta_feature`, `netherrack_replace_blobs`, `huge_fungus`, базальтовые столбы, светокамень, лианы, растительность), крепость (`NetherFortressPieces`), бастион (jigsaw), окаменелости (`nether_fossil`) и руины портала Ада. Сверка с `server.jar` в 16 чанках: сид 12345 — 9 клеток (8 `magma_block` против `blackstone` строкой на краю окна, где пересекаются пятна соседних чанков, и один гриб), сид 1 — 4 (две пары `gravel`/`lava`). Сид −6843522070344381082 у (513, 303), 64 чанка с багровым лесом и бастионом: 0 клеток биомов, 1505 блоков — грибы и лианы на стыках чанков, зависят от порядка декорации (два прогона самой ванили расходятся так же). Стенд сверяет биомы; нилий, ставший незерраком под ножкой от случайного тика живого сервера, считается тиком. `count_on_every_layer` — по `CountOnEveryLayerPlacement` 26.3 (`count`, слои пола от `MOTION_BLOCKING`, точки собираются до обхода, как в `FeaturePlacer`), корни и грибы Ада проверяют свой тег `supports_<блок>`, `start_height` с константой не тратит бросок, процессор `rule` понимает `position_predicate` (`linear_pos`, `axis_aligned_linear_pos` от опорной точки старта) — без него `high_rampart` стирал верх стен бастиона. Жидкости, которые у ванили уже потекли на тиках сервера, стенд считает отдельно | | Край: измерение | готово | M7 | `minecraft:the_end` в `level.dat`, `DimSpec` с `min_y` 0 и высотой 256, `/execute in minecraft:the_end`. Путь туда и обратно — в строке «Порталы в Ад и Край». Дракон, кристаллы и бой — в «Боссах» | | Генерация островов Края | готово | M7 | Плотности `end_islands` (`SimplexNoise` от `LegacyRandomSource(seed)` с пропуском 17292) и `distance_to_point`, `TheEndBiomeSource` (радиус 64 чанка — `the_end`, дальше по `erosion`), фичи `end_spike` (10 шипов по сиду, клетки у `guarded`), `end_platform`, `end_gateway`, `chorus_plant`, `end_island`, город Края (`EndCityPieces`, корабль, лут `end_city_treasure`). Сверка с `server.jar` на сиде 12345: главный остров (16 чанков вокруг 0,0) — 0 расхождений; окно 16 чанков вокруг города из нашего `/locate` (`[992, ~, 384]`) — 0. Сущности генерации (кристаллы, шалкеры, рамка с элитрами) не пишутся, как у прочих структур | | Скорость генерации | готово | M7 | Стенд `examples/chunk_rate` (радиус 10, сид 12345, 441 чанк, Ryzen 9 7945HX): 52.9 чанка/с в один поток и 364.6 на 31 потоке (было 39 и 62.8). Без смены результата: раскладка структуры собирается один раз на все потоки (`OnceLock` на ключ); `getBaseHeight` и `getBaseColumn` — один столбец шума с акифером, как у ванили, вместо целого чанка с поверхностью; разъёмы jigsaw хранят фронт и верх для всех поворотов заранее; живые карты высот прото-чанка строятся один раз и идут за записями фич, а не сканируются заново каждым регионом 3×3; `with_prop` — сдвиг номера состояния по раскладке свойств блока вместо перебора (тест `with_prop_layout_matches_scan` сверяет оба пути на всех состояниях). `CHUNKWAKE_PROF=1` печатает время стадий прото-чанка и фич дороже 300 мкс. Сверка после этого: сид 12345 — 6642 блока, контейнеры 0; древний город — 13959; Ад — 9 и 4. Повторный замер после жидкостей и биома поверхности: 52.0 чанка/с в один поток, 530–570 на 31 потоке; эталон сида 12345 пересоздан 2026-11-03, сверка с ним — 7767 (у HEAD до правок то же; лишнее — пышные пещеры, в которых ваниль сама расходится между прогонами), Ад — 9 и 4. Живой путь (бот `crates/bots/examples/fly_probe.rs` летит по прямой и считает долю квадрата обзора у клиента; обзор 32, симуляция 24): тик держал 0.2–1.2 с на каждом свежем кольце — отложенные тики жидкостей с краёв чанков писали в несгенерированного соседа, и `set_block` генерировал его прямо на тике; сессия игрока, которая раздаёт задания пулу и свету, стояла вместе с тиком. Теперь тики жидкостей, расписание блоков и воронки идут только в тикающих чанках (`ChunkMap.isPositionTicking`: в симуляции и с загруженными восемью соседями), а тики остальных лежат по чанку и не перебираются каждый тик (`TickQueue::unpark`). Случайные тики берут флаг `isRandomlyTicking` из таблицы по номеру состояния и пропускают секции без тикающих блоков по палитре (было ~30 мс на тик при симуляции 24). Итог: весь обзор 32 у стоящего игрока — за 14 с вместо 33, полёт 30 блоков/с держит 100 % обзора | | Генерация из датапака | нет | M7 | `worldgen/*`, после встроенного оверворлда | | Точка спавна по рельефу | готово | M7 | `NoiseSpawnFinder` по байткоду: `spawn_target` из `noise_settings` (две точки `continents`/`erosion`/`ridges`/`temperature`/`vegetation`), квантование `Climate.quantizeCoord`, `Parameter.distance` по выходу за диапазон, фитнес `min * 2048^2 + x^2 + z^2`. Первый кандидат — `(0, 0)`, затем два радиальных прохода: шаг 512 до 2048 и шаг 32 до 512, и второй проход начинает от лучшей точки первого (`searchOrigin = result.location()`). Высота — первый твёрдый блок столбца этой точки плюс один. Сверка с `server.jar` через `NoiseSpawnFinder`: 5 сидов из 5 совпали бит в бит — (103, −28), (163, 170), (−31, 6), (7, 23), (0, 0). Стенд `spawn_probe` | | Граница мира | готово | M7 | `/worldborder get|set|add|center|damage|warning|buffer`, `initialize_border` при входе, анимация сдвига (`set_border_lerp_size`), сохранение в `gamerules.json` и ванильные пределы (`MAX_SIZE` 59999968, центр до 29999984). Урон за границей по `LivingEntity.baseTick`: `max(1, floor(-dist * damage))` при `dist = getDistanceToBorder + safeZone` и `damage > 0`, тип урона `minecraft:outside_border` (Bukkit `WORLD_BORDER`), тест `border_distance_and_damage`. Поиск выхода портала начинается с `clampToBounds` после масштабирования координат и отбрасывает найденные порталы вне границы. Новый портал ставится только там, где рамка 4×5 целиком внутри границы; запасная площадка 2×3 также проверяется, ближайшее допустимое место ищется спиралью. Движение игрока, лодок, мобов, вагонеток, выброшенных предметов, сфер опыта и зажжённого TNT останавливается коробкой у границы; оказавшиеся снаружи после сжатия могут вернуться. Стрелы отскакивают от границы со скоростью −10% исходной (`ProjectileDeflection.REVERSE` при силе 0.2), снежки, яйца, зелья, жемчуг Края и ракеты сталкиваются с ней при полёте. Глаз Края свободно проходит границу, как в `EyeOfEnder.tick` 26.3. Граница хранится отдельно для каждого измерения: `/worldborder` меняет текущий мир, обновления получают только его игроки, при переходе посылается граница нового мира; старое поле `border` загружается как граница обычного мира | ## Редстоун Ядро ниже — коротко и со ссылкой на M5, без пересказа того абзаца. | Фича | Статус | Этап | Заметка | |---|---|---|---| | Провод, редстоун-блок, факел, повторитель, лампа | готово | M5 | Затухание, перегорание, задержка; подробности в M5 | | Рычаг, кнопка, нажимные плиты | готово | M5 | Плиты нажимают игроки и живые мобы; предметы нажимают деревянные и утяжелённые плиты, но не каменные. Утяжелённые плиты считают сущности отдельно. Рычаг, кнопка и плиты при включении и выключении посылают `block_activate` / `block_deactivate`; изменение ненулевой силы утяжелённой плиты нового события не даёт. Отпускание кнопки и плиты проверено `sculk_events_probe` | | Компаратор: сравнение, вычитание, сундук, печь | готово | M5 | Другие источники — отдельная строка | | Наблюдатель и поршни | готово | M5 | В том числе флай-машины | | Двери, люки и калитки по сигналу | готово | M5 | | | Воронка и выбрасыватель | готово | M5 | Раздатчик — отдельно | | Форма рельсов | готово | M5 | Вагонетки — строка «Вагонетки и питаемые рельсы» | | Плиты от мобов | готово | M5 | Живые мобы жмут все плиты, как игроки | | Раздатчик | частично | M5 | Вёдра, огниво, стрелы, снежки, яйца, броня, яйца призыва (`DISPENSE_EGG`), ножницы по овцам, ТНТ, костная мука по культурам и саженцам, фейерверк, взрывные и оседающие зелья. Лодка (`BoatDispenseItemBehavior`: на `0.5625 + ширина/2` вперёд, вода спереди или вода под воздухом, иначе выброс; без своей физики сразу ложится на поверхность воды) и вагонетка (`MinecartDispenseItemBehavior`: на 1.125 вперёд, рельс спереди +0.1/+0.6 на подъёме, рельс под воздухом −0.9/−0.4), проверено ботом `dispense_probe`. Дым `2000` после любого особого действия, как в `DefaultDispenseItemBehavior.dispense`. Стеклянная бутылка (`$9`): мёд из полного улья, вода из любой воды впереди, иначе выброс со щелчком осечки `1001`; светокамень (`$10`) заряжает якорь возрождения, у полного — осечка; соты (`$12`) вощат медь (`3003`); бутылка воды (`$13`) делает из земли грязь, другое зелье выбрасывается с двойным щелчком внутреннего `dispense`; ножницы сначала по полному улью (соты из `harvest/beehive`), потом по овце; ведро рыхлого снега — только в пустоту; пустое ведро черпает источник, воду из заводнённого блока и рыхлый снег (`2001`) без звука наполнения; полное ведро, которому некуда вылиться, — двойной щелчок. Остаток (бутылка, ведро) — `consumeWithRemainder`: заменяет последний предмет, иначе в свободный слот, иначе вылетает. Сверено ботом `dispense_items_probe` с `server.jar` (18 шагов раздатчика и 4 игрока; расходится только порядок пакетов слотов инвентаря — у ванили они уходят в конце тика). Нет: шалкера (у шалкера нет контейнера), черепа иссушителя и тыквы как ритуалов (идут броней на голову), вёдер с рыбой, брони на стойку, сундука на осла, щётки, опыта, огненного и ветряного заряда; оседающее зелье пока применяет эффект при ударе без облака | | Улей, якорь возрождения, грязь | частично | M7 | Игрок и раздатчик: ножницы по полному улью (`honey_level` 5) — три соты из `harvest/beehive` через `popResource`, звук `beehive.shear`; бутылка — мёд, звук `bottle.fill` слышат все, кроме игрока (`playSound(player, …)`); улей сбрасывается в 0 (`resetHoneyLevel`). Светокамень по якорю — `charges` +1, звук `respawn_anchor.charge`, в творческом не тратится (`ItemStack.consume`). Бутылка воды по земле (`convertible_to_mud`, не снизу) — грязь, пять брызг `splash`, звуки `generic.splash` и `bottle.empty`, в руке пустая бутылка. `/give` разбирает `potion_contents` (`"minecraft:water"` и `{potion:…}`). Якорь без светокамня (`useWithoutItem`, только основная рука): в Аду (`respawn_anchor_works`) — точка возрождения, сообщение `block.minecraft.set_spawn` и звук `respawn_anchor.set_spawn`, повторный клик по той же точке — ничего; вне Ада — блок убран без дропа и взрыв силой 5 с огнём, а вода рядом (`isWaterThatWouldFlow`) гасит лучи в клетке якоря. Пустая бутылка рукой (`BottleItem.use`): луч `SOURCE_ONLY` на дальность взаимодействия по взгляду из пакета `use_item`, вода (и заводнённый блок) — бутылка воды по `createFilledResult`, звук `bottle.fill` всем, кроме игрока, вибрация `fluid_pickup`, инвентарь целиком (`sendAllDataToRemote`); сверено ботом `dispense_items_probe`. Нет: дыхания дракона из облака, пчёл (злить и выпускать некого, костёр под ульем не проверяется), отдельной причины смерти `badRespawnPoint` (идёт как обычный взрыв) | | Датчик дневного света | готово | M7 | `DaylightDetectorBlock.updateSignalStrength`: сигнал — небесный свет клетки (под открытым небом 15), у инвертированного — `15 - sky`. Угол солнца берётся из таймлайна `OVERWORLD_DAY`: ключевые кадры `6000 → 0°` и `6000 → 360°` на периоде 24000, easing `symmetricCubicBezier(0.362, 0.241)` — 4 итерации Ньютона и бисекция, как в `EasingType.CubicBezier`. После полудня угол тянется к `2π` с весом 0.2, и `power = round(sky * cos(angle))`, зажим 0..15. Тест `daylight_detector_power_matches_vanilla`: полдень 15, полночь 0, через сутки снова 15, инверсия угол не учитывает | | Мишень | готово | M7 | `TargetBlock.updateRedstoneOutput` по байткоду: попадание выставляет `power` по `getRedstoneStrength` — `max(1, ceil(15 * clamp((0.5 - dist) / 0.5, 0, 1)))`, где `dist` берётся по двум осям, кроме оси попадания (Y → X/Z, Z → X/Y, X → Y/Z). Ось попадания — доминирующая компонента скорости снаряда. Сброс через `ACTIVATION_TICKS_ARROWS` 20 тиков, сигнал отдаётся как `power` (`weak`-выход 15). Тест `target_power_matches_vanilla`: центр 15, смещения 12/10/6/3, край 1 — числа сняты с той же формулы ванили на double | | Растяжка | готово | M7 | `TripWireHookBlock.calculateState` по байткоду: проход от крюка в сторону `facing` (это его `facing`, второй крюк должен смотреть навстречу), `WIRE_DIST_MAX` 42, `attached &= receiverPos > 1`, `powered |= wireArmed && wirePowered`, затем `powered &= attached`. Разоружённая ножницами нить (`disarmed=true`) сигнала не даёт, а `attached` снимает только при вызове из этой нити (`if (i != wireSource) continue; attached &= wireArmed`) — вызов из крюка его не трогает. Посторонний блок снимает натяжение, но поиск второго крюка продолжается. Нить: `TripWireBlock.getStateForPlacement` тянется к соседней нити и к крюку, смотрящему навстречу; `updateShape` пересчитывает стороны по соседям; `checkPressed` ловит сущности формой `Block.column(16, 1, 2.5)` натянутой и `Block.column(16, 0, 8)` провисшей — игрок 0.6×1.8, моб по своим габаритам, предмет 0.25; перепроверка раз в 10 тиков, отпустило — через 1. Крюк даёт 15 слабым выходом во все стороны и сильным только в `facing` (`ownSignal`, `getDirectSignal`). Звуки `tripwire.click_on`/`click_off`/`attach`/`detach` с высотой `1.2 / (random * 0.2 + 0.9)`. Тесты `tripwire_chain_matches_vanilla`, `tripwire_hook_signal_is_directional`, `tripwire_shape_matches_vanilla`, `tripwire_connects_like_vanilla`, `tripwire_hook_sounds`. Живой прогон на сервере: цепь крюк–нить–нить–крюк, корова на нити — записалось `attached=true powered=true` у обеих нитей. Живой прогон `tripwire_probe`: обычный разрыв даёт краткий сигнал обоим крюкам, ножницы снимают натяжение без сигнала; бот проверяет оба исхода и вибрации крюков калиброванным сенсором (активация 10, отсоединение 9) | | Нотный блок | готово | M7 | Инструмент выбирается как `NoteBlock.setInstrument`: блок над (только если `worksAboveNoteBlock` — головы мобов и игрока), иначе блок под, иначе `harp`; голова **под** нотным блоком даёт `harp`, потому что сама «работает над блоком». Сами инструменты выгружает новый экстрактор `ChunkwakeInstruments` в `reports/instruments.csv` (796 блоков с нестандартным инструментом): `instrument()` — свойство блока, поэтому строка одна на блок, и имя берётся из реестра блоков, а не id состояния — порядок id в реестре состояний не совпадает с `blocks.json`, на этом звучал `bass` вместо `basedrum`. Звук `playNote` берёт инструмент из свойства **состояния** `instrument=…`, а не по имени блока: у самого `note_block` в ванили `instrument(BASS)`, и поиск по имени давал `bass` всегда. `playSeededSound` с `SoundSource.RECORDS`, громкость 3, высота `2^((note-12)/12)` только у настраиваемых инструментов, у голов 1. Частица ноты — от `(0.5, 1.2, 0.5)` со скоростью `note/24`. Сигнал играет ноту только на фронте (`neighborChanged`), клик меняет `note` по кругу из 25 и играет, удар играет без смены. Тесты `note_pitch_matches_vanilla`, `note_instrument_matches_vanilla`, `note_signal_only_on_rising_edge`, `note_plays_in_vanilla_conditions`, `note_visuals_match_vanilla`, `note_instrument_names_round_trip`, `instruments_report_matches_vanilla` (сверка с `Blocks.java`: камень — `basedrum`, доски — `bass`, стекло — `hat`, песок — `snare`, сам нотный блок — `bass`, череп скелета — `skeleton`, череп дракона — `dragon`). Живой прогон: рычаг дал фронт сигнала и `instrument="basedrum" note=0 pitch=0.5 sound=block.note_block.basedrum` при камне под блоком. `note_block_play` (частота 10) отправляется при клике, ударе и фронте сигнала, если блок сверху не глушит ноту; `sculk_events_probe` проверил все три пути и глушение камнем. Не сделано: голова игрока со своим `note_block_sound` — звук из блок-сущности не пишется, такая голова молчит | | Скалк | частично | M7 | Сенсор и калиброванный сенсор: `activate`/`deactivate`/`tick` по байткоду (`ACTIVE_TICKS` 30, `COOLDOWN_TICKS` 10, фазы `inactive`/`active`/`cooldown`), мощность `getRedstoneStrengthForDistance` = `max(1, 15 - floor(15/8 · dist))`, радиус слуха 8. Слабый выход — `power`, сильный только вверх (`getDirectSignal`); калиброванный молчит в сторону `facing` и фильтрует частоты сигналом сзади (`getBackSignal` + `canReceiveVibration`). Частоты событий — полная таблица `VIBRATION_FREQUENCY_FOR_EVENT` (36 событий, шаг 1 … смерть и взрыв 15) плюс `resonate_1…15`. Резонанс аметиста: `tryResonateVibration` в радиусе 1, высота по таблице тонов `[0,0,2,4,6,7,9,10,12,14,15,18,19,21,22,24]`, событие `resonate_`. Компаратор активного сенсора отдаёт частоту последней вибрации, неактивного — 0. Вибрации шлёт шаг сущности по сенсору (`stepOn`, частота 1), установка блока игроком и разрушение (`block_place` 13, `block_destroy` 12), а также открытие и закрытие дверей, люков и калиток (`block_open` 10, `block_close` 9). Тесты `vibration_frequencies_match_vanilla` (36 событий), `sculk_sensor_power_matches_vanilla`, `sculk_phase_transitions_match_vanilla`, `calibrated_sculk_sensor_rules`, `resonance_matches_vanilla`. Живой прогон: корова на сенсоре — `sculk: hear dist=0.5 frequency=1`, сенсор включился; `sculk_events_probe` подтвердил активацию от урона игрока, приземления игрока без урона и приземления коровы. Не сделано: крикун (`sculk_shrieker`, призыв стража) и катализатор (распространение скалка и опыт) — только блоки в реестре и фича `sculk_patch`; общей системы `gameEvent` в сервере нет; сейчас доходят шаг, приземление игрока и моба (`hit_ground`), реально применённый урон игроку и мобу (`entity_damage`), смерть игрока и моба от урона (`entity_die`, частота 15; проверены игрок и корова в лаве в `sculk_events_probe`), запуск снарядов через общий `launch` (`projectile_shoot`, частота 3) и их попадание в блок (`projectile_land`, частота 2; оба события проверены снежком и калиброванным сенсором в `sculk_events_probe`), установка, разрушение, смена пластинки в проигрывателе (`block_change` при ручном действии и через воронку), разоружение растяжки ножницами (`shear`), срабатывание и соединение крюков растяжки (`block_activate` / `block_deactivate` / `block_attach` / `block_detach`), ручное переключение дверей, люков и калиток; открытие и закрытие дверей и калиток редстоуном (`block_open` / `block_close`, подтверждено `sculk_events_probe`); люк при переключении редстоуном вибрацию не посылает — как в `TrapDoorBlock.neighborChanged`. Рычаги, кнопки и нажимные плиты (`block_activate` 10 / `block_deactivate` 9, включая отпускание кнопки и плиты), игру нотного блока (`note_block_play` 10), открытие и закрытие обычных, медных сундуков, сундуков-ловушек и бочек (`container_open` 10 / `container_close` 9, только первый и последний зритель). `sculk_events_probe` подтвердил открытие и закрытие сундука и бочки; открытие воронки сенсор не будит. Для остальных игровых событий покрытие пока неполное. `/setblock` вибрацию не даёт — как в ванили (флаг 258 без `BLOCK_UPDATE`) | | Медные сундуки | готово | M7 | Все восемь вариантов подключены к окну сундука и `minecraft:chest` блок-сущности: 27 слотов, 54 у двойного, сохранение и выпадение `Items`, компаратор, открытие/закрытие и звук. Сундуки разных стадий соединяются; при постановке выбирается менее окисленная стадия, а при вощении, очистке топором и случайном окислении обе половины меняются вместе без потери инвентаря. Окисление учитывает ближайшие медные блоки и правило `random_tick_speed`; воск расходуется, топор изнашивается. Топор и сота действуют на стадии предмета, как у ванили: без приседания клик открывает сундук, с приседанием — счищает или вощит | | Превращение блоков инструментами | готово | M7 | Реестр `block_transformer` 26.3 из данных и компонент предмета `minecraft:block_transformer` (`BlockTransformer.transformBlock`): топор снимает кору (25 правил), счищает окисление и воск (`copy_properties` — свойства блока сохраняются), мотыга пашет `turns_into_farmland` и грубую землю при воздухе сверху, кроме клика снизу, и превращает корнистую землю в землю с выпадением `till/rooted_dirt` из кликнутой грани (`popResourceFromFace`), лопата делает тропинку. Порядок ванили: сначала действие блока (дверь, люк, сундук), потом предмет; щит во второй руке без приседания блокирует; не в приключении и не наблюдателем. Звук всем, кроме игрока, события частиц 3005/3003/3004, `block_change`, износ `item_damage_per_use`, пересчёт соединений соседей. Сота (`HoneycombItem.WAXABLES`) вощит все 60 стадий меди с переносом свойств и тратится. Не перенесено: триггеры достижений (`item_used_on_block`) | | Сундук-ловушка | готово | M7 | При открытии, закрытии и выходе игрока даёт слабый сигнал во все стороны силой `min(15, число зрителей)` и сильный в блок под собой. Обе половины двойного сундука учитывают одних и тех же зрителей; соседи пересчитываются сразу. Тесты `trapped_chest_signal_tracks_viewers`, `chest_viewers_close_both_halves_and_disconnect` | | Эндер-сундук и шалкер-ящики | частично | M7 | Эндер-сундук: 27 личных слотов в `EnderItems` игрока (`PlayerSave`/playerdata), а не в блок-сущности; окно `Kind::EnderChest`, при клике содержимое пишется в состояние игрока. Шалкер: блок-сущность `minecraft:shulker_box`, окно `container.shulkerBox`, 27 слотов; при установке предмета содержимое компонента `minecraft:container` переносится в `Items`, при поломке, взрыве и `setblock destroy` возвращается в компонент выпавшего предмета, а не высыпается; шалкер в шалкер не кладётся. Компонент `container` разбирается и пишется в сеть и NBT. Крышка шалкера открывается и закрывается по событию блока 1, звук `block.shulker_box.open|close`. Проба `storage_probe`: перенос в оба контейнера, повторное открытие, выход/вход для эндер-сундука, дроп шалкера → подбор → установка с содержимым. Нет: `minecraft:lock`/`container_loot` | | Лектор | готово | M7 | Книга кладётся кликом и сохраняется в блок-сущности `minecraft:lectern`. Открывается ванильное меню с книгой: кнопки листают страницы и выбирают страницу по номеру, кнопка «забрать» возвращает книгу в инвентарь. Страница синхронизируется между зрителями, ограничивается длиной книги и сохраняется. При перелистывании лектор выдаёт импульс 15 на 2 тика, компаратор показывает прогресс чтения по `floor(pageProgress × 14) + 1` | | Детекторные рельсы | частично | M7 | Форма рельсов общая (`Placement::rails`), сигнал 15 слабым выходом во все стороны и сильным только вверх. Рамка поиска вагонетки — `DetectorRailBlock.getSearchBB`: отступ 0.2 по горизонтали и до `y + 0.8`, тест `detector_rail_search_box`. Вагонетка на рельсе включает его (`entityInside` → `checkPressed`), тик перепроверяет нажатие раз в `PRESSED_CHECK_PERIOD` 20 тиков и держит сигнал, пока вагонетка в рамке; проверено живым ботом. Компаратор от нажатого рельса читает заполненность вагонетки с сундуком или воронкой (`getAnalogOutputSignal`); `DetectorRailBlock.checkPressed` не посылает `gameEvent` при включении и выключении (сверено по байткоду 26.3). Вагонетки с командным блоком нет | | Молниеотвод | готово | M7 | `LightningRodBlock`: удар включает `powered` на `ACTIVATION_TICKS` 8 тиков, событие `3002` с осью `facing`, соседи обновляются в сторону, противоположную `facing`, то есть в блок-опору. Тик снимает сигнал. Слабый выход 15 во все стороны, сильный только в `facing`. Молния ищет ближайший стержень в радиусе `RANGE` 128 и только не ниже точки удара (`redstone::nearest_rod`, тест `nearest_lightning_rod_rules`); точка входа — `PlayPlan::lightning_strike` и `/summon minecraft:lightning_bolt`. Тесты `tail_blocks_signal_directions`, `tail_block_constants`, `nearest_lightning_rod_rules`. Живой прогон: `summon lightning_bolt` у стержня дал `rod: powered on`, `Summoned new Lightning Bolt`. Не сделано: искры `electric_spark` в грозу (`animateTick`) и урон от молнии рядом со стержнем | | ТНТ | готово | M7 | Блок поджигается огнивом, огненным зарядом, раздатчиком и редстоуном; `unstable` загорается при ломке. Сущность с фитилём 80 тиков движется под действием гравитации, затем взрыв лучами разрушает блоки, выбрасывает содержимое контейнеров, наносит урон и поджигает соседний ТНТ с укороченным фитилём. Вода и неразрушаемые блоки экранируют взрыв. Вагонетка с ТНТ относится к строке вагонеток ниже | | Лодки и плоты | частично | M7 | `BoatItem.use`: луч с жидкостями на 5 блоков, лодка в точке попадания с поворотом игрока (верх воды — `FluidState.getHeight`: источник 8/9, течение (8−уровень)/9; на земле — верх коробки коллизии, поэтому трава и опавшие листья пропускаются: формы контура в данных нет), без места над точкой — не ставится, тратится не в креативе; тип сущности = имя предмета (все дерева, плоты, с сундуком). Посадка `interact` без шифта, мест 2 (с сундуком 1). Водителем правит его клиент: `move_vehicle` двигает лодку, остальным — `teleport_entity`, за лодкой идут игрок и подгрузка чанков; вёсла `paddle_boat` → данные 11/12. Шифт снимает (`Player.rideTick`), место высадки — `getDismountLocationForPassenger` (шаг за борт по взгляду, над водой или без опоры — на лодку). Удар — `VehicleEntity.hurtServer`: урон ×10, минус 1 за тик, больше 40 — ломается с предметом (`entity_drops`), в креативе исчезает с первого удара. Смерть и выход снимают с лодки. Пишутся в `entities/*.mca` рядом с мобами (`id`, `Pos`, `Rotation`, `UUID`, `CustomName`), живой прогон `boat_probe`: поставить, проехать, сойти, разбить, перезапуск. Нет: физики пустой лодки (дрейф по течению, падение, плавучесть), столкновений и толкания, инвентаря лодки с сундуком, посадки мобов, поводков, урона от падения в лодке, селекторов `@e[type=oak_boat]`, повторной отправки пассажиров игроку, к которому всадник пришёл в обзор позже | | Вагонетки и питаемые рельсы | частично | M7 | Обычная вагонетка по `OldMinecartBehavior`: ставится на рельс (`MinecartItem.useOn`, +0.5 на подъёме), едет по форме рельса (`getPos`/`moveAlongTrack`), гравитация 0.04, скат 0.0078125, предел 8/20 блока за тик (в воде 4/20), с седоком ×0.75 и трение 0.997 (пустая 0.96), сохранение энергии. Питаемый рельс разгоняет на 0.06, тормозит без сигнала и толкает от упора на 0.02; сигнал идёт по цепочке до 8 рельсов (`findPoweredRailSignal`), то же у активатора; включённый активатор высаживает седока. Сесть, шифт — сойти по `DismountHelper`, игрок толкает вагонетку, сход с рельсов — движение с коллизией. Удар по `VehicleEntity.hurtServer` (урон 40, в творческом — сразу), выпадает вагонетка. Сохраняется в `entities/*.mca` и переживает перезапуск; проверено ботом `cart_probe`. Раздатчик ставит вагонетку на рельс. Позиция рассылается от сдвига в квадрате 7.6293945e-6 (`ServerEntity.sendChanges`). Виды (проверено ботом `cartvar_probe`, с перезапуском): с сундуком (27) и воронкой (5) — окно по клику, трение `0.98 + (15 − заполненность)·0.001` (`AbstractMinecartContainer`), при поломке выпадает содержимое, `Items` пишется; воронки-блоки берут из вагонетки и кладут в неё (`getEntityContainer`), выбрасыватель тоже. Воронка-вагонетка каждый тик тянет из контейнера над собой, без него — предметы в зоне над ней, затем задевающие её; включённый активатор выключает (`Enabled`). Печная: уголь из тега `furnace_minecart_fuel` +3600 до 32000, тяга от игрока поворачивает вдоль скорости (`calculateNewPushAlong`), предел ×0.5 (в воде ×0.75), горение — данные 13 (`PushX`/`PushZ`/`Fuel`). ТНТ: включённый активатор поджигает (фитиль 80, событие 70), удар о стену на скорости, падение с 3 блоков и поломка на скорости или взрывом взрывают/поджигают; сила `4 + rnd·1.5·min(√v², 5)`, подожжённая не рушит рельсы и блок под ними; взрывы бьют и отбрасывают вагонетки. Вагонетки толкают друг друга (`pushAndPickupEntities` → `AbstractMinecart.push`/`pushOtherMinecart`): коробка +0.2, толчок 0.05, только вдоль поворота толкающей (|cos| ≥ 0.8); печная тянет другую, обычные делят среднюю скорость. Мобов движущаяся вагонетка не подбирает (`startRiding`) — мобы в вагонетках не сделаны. Нет: вагонетки со спаунером и командным блоком, `NewMinecartBehavior` (экспериментальный) не делался | | Компаратор от остальных источников | частично | M7 | Торт: `14 - 2 × bites`; пустой котёл: 0, вода и рыхлый снег: `level`, лава: 3; компостер: `level` (0–8); улей и пчелиное гнездо: `honey_level` (0–5); якорь возрождения: `⌊charges × 15 / 4⌋` (0, 3, 7, 11, 15). Сигнал читается напрямую и через проводящий блок (`cake_and_cauldrons_feed_comparator_through_block`, `composter_and_hives_feed_comparator_directly_and_through_block`, `respawn_anchor_charge_feeds_comparator_directly_and_through_block`). Рамка с предметом за проводником: `поворот + 1` (0 — пустая), берётся максимум с сигналом блока, как в `ComparatorBlock.getInputSignal`; проверено ботом `frame_probe` против `server.jar`. Проигрыватель: `comparator_output` пластинки (строка «Проигрыватель»). Крафтер: число занятых и выключенных слотов (строка «Крафтер») | | Крафтер | готово | M7 | `CrafterBlock`, `CrafterBlockEntity`, `CrafterMenu`. Ставится с `orientation` от взгляда (фронт против взгляда, верх по горизонтали при взгляде вверх или вниз). Сигнал без квазисвязности: на передний фронт `triggered` и через 4 тика сборка. Без рецепта событие 1050. С рецептом `crafting` на 6 тиков, результат и остатки вперёд: в контейнер (в крафтер по одному) или предметом со скоростью 6, события 1049 и 2010. Каждый слот сетки −1. Воронка и выбрасыватель кладут по `canPlaceItem` (выключенный и полный слот нельзя, занятый — только если дальше нет пустого или меньшей такой же стопки). Компаратор: занятые и выключенные слоты. Окно `crafter_3x3`: результат только для показа, данные 0–8 (слот выключен) и 9 (`triggered`), `container_slot_state_changed` выключает только пустой слот; `disabled_slots`, `triggered`, `crafting_ticks_remaining` в блок-сущности. Сверка ботом `crafter_probe` с `server.jar`: состояния блока, события, выпавшие доски (место, данные), сундук впереди, сила компаратора, содержимое и данные окна совпадают | | Проигрыватель | частично | M7 | `JukeboxBlock`, `JukeboxBlockEntity`, `JukeboxSongPlayer`: пластинка из руки (`jukebox_playable`, песни из `data/minecraft/jukebox_song`, тратится одна не в творческом), пустой рукой — наружу с высоты 1.01 и сдвигом до ±0.35; шифт с предметом блок не трогает. Событие 1010 с сетевым номером песни и 1011 при остановке, `has_record` блока, нота раз в 20 тиков (`count` 0, цвет `nextInt(4)/24`), конец через `ceil(секунды·20) + 20` тиков. Пока играет — источник сигнала, слабый выход 15 во все стороны (провод тянется к нему, воронку под ним запирает); компаратор читает `comparator_output` пластинки. Воронка и выбрасыватель кладут пластинку в пустой проигрыватель, воронка снизу забирает её, если у неё есть пустой слот. Поломка и взрыв выбрасывают пластинку. `RecordItem` и `ticks_since_song_started` пишутся в блок-сущность (место в песне — в копию столбца при сохранении), после перезапуска песня продолжается без повторного 1010. Сверка ботом `jukebox_probe` с `server.jar`: события, номера песен, сигнал провода, сила компаратора (2 у `cat`, 1 у `13`), ноты и высота выпавшей пластинки совпадают; ваниль шлёт смену блока в конце тика, мы — сразу. Смена пластинки отправляет слышимое сенсором `block_change` (частота 11): живой `sculk_events_probe` подтвердил вставку и извлечение рукой, а также вставку через воронку. Сами `jukebox_play` и `jukebox_stop_play` как общие игровые события пока не рассылаются; в ваниле сенсор их не слушает. Нет: проигрывателя с пластинкой из предмета (`block_entity_data`), статистики `play_record` | | Картины | готово | M7 | `minecraft:painting`: варианты из `data/minecraft/painting_variant/*.json` (размер, asset id); выбирается самый большой, который влезает, из подходящих — случайный. Проверяются объём картины, опора за ней (`solid`/диод) и пересечение с другими картинами. Вариант передаётся клиенту в метаданных (индекс 9, сериализатор 34) как direct holder, поэтому реестр клиенту не нужен; `facing` — 2D-направление в NBT. Удар снимает картину и роняет предмет, взрыв и пропавшая опора — тоже; в творческом дропа нет. Проба `painting_probe`: стена, установка, спавн сущности, удар и дроп | | Знамёна | частично | M7 | `BannerBlockEntity`: при установке из предмета — `patterns` и `CustomName` (плюс пустой `components`, как у ванили), рассылка `block_entity_data`; при поломке рукой, взрывом и `setblock destroy` узоры и имя переходят на выпавшее знамя (`copy_components` таблицы лута). `give` разбирает `banner_patterns`. Сверка с `server.jar` (бот `banner_probe`): данные блок-сущности и выпавший предмет совпадают по содержимому (порядок ключей NBT и компонентов слота другой). Нет: `item_name`, `tooltip_display`, `rarity` и прочих компонентов в блок-сущности, щита со знаменем | ## Сущности | Фича | Статус | Этап | Заметка | |---|---|---|---| | Корова, свинья, овца, курица | частично | M5 | Физика, ИИ, размножение, стрижка, молоко, яйца, яйцо призыва, запись в `entities/*.mca` (`mob_to_nbt`). Урон от падения по `safe_fall_distance` (в воде и лаве сбрасывается), молнии (5 урона, огонь 8 с) и окружения; краска меняет цвет овцы, табличка с именем ставит `CustomName` и `PersistenceRequired`. Других мирных видов нет; монстры — строка «Враждебные мобы» | | API мобов для плагинов | частично | M5 | `spawnEntity`/`spawn`, `getEntities`/`getLivingEntities`/`getNearbyEntities`, `MobImpl` поверх запросов `mob …`; шесть событий (удар, смерть, клик, спавн, размножение, стрижка). Нет атрибутов, эффектов, снаряжения, `Pathfinder`, `getEntity(UUID)` | | Заселение чанка и спавн по тику | частично | M5/M7 | `populate_chunk` один раз сажает животных при генерации по категории `creature` биома. Спавн по тику — `NaturalSpawner.spawnForChunk`: попытки в чанках симуляции в 128 блоках от игрока, лимит `max × чанки / 289` (монстры 70, существа 10), где чанки — квадраты 17×17 вокруг игроков (`naturalSpawnChunkCounter` радиуса 8, от дальности симуляции не зависит: один игрок — полный лимит), по мобам, которые могут исчезнуть, в чанках симуляции (в ванили — во всех загруженных); монстры — каждый тик (не в мирной сложности, правила `spawn_mobs` и `spawn_monsters`), существа — раз в 400 тиков. Точка — от дна мира до поверхности + 1, три группы по `ceil(4·r)` шагов со сдвигом `nextInt(6) − nextInt(6)`, вид по весам `spawns_by_category` биома этой точки (виды, которых нет в сервере, остаются в весах и не спавнятся — доля зомби ванильная), не больше 4 за заход, не ближе 24 блоков к игроку и точке спавна мира. Место `ON_GROUND`: прочная верхняя грань опоры (не лёд, листва, стекло, магма, бедрок, барьер), клетка и клетка над ней не полные, без жидкости, огня и рельсов, рамка моба свободна. Монстр — `isDarkEnoughToSpawn` (небесный свет против `nextInt(32)`, блочный свет 0 в оверворлде и до 15 в Аду, сырая яркость не выше `uniform(0, 7)`, в грозу затемнение 10), животное — трава под ногами и свет больше 8. `CreatureSpawnEvent` с причиной `NATURAL`. Исчезновение `Mob.checkDespawn`: дальше 128 блоков — сразу, дальше 32 после 600 тиков без дела — с шансом 1/800, в мирной сложности монстры уходят; с яйца, из команды, от плагина и с именем моб остаётся (`PersistenceRequired` в NBT). Логика — чистые функции в `crates/net/src/mobs/mod.rs` с тестами. Сверка — `night_probe` на свежих плоских мирах: ваниль и мы держат около 70 монстров у одного игрока, доли видов близки (ваниль: паук 37 %, зомби 29 %, крипер 16 %, скелет 16 %; мы: 32, 21, 16, 32 %) — точнее не сравнить, разброс между запусками велик. Нет: амбиента и водных категорий, местных лимитов на игрока (`LocalMobCapCalculator`), спавна структур (крепость Ада, хижина ведьмы, монумент), `spawn_costs`, детёныша зомби на всю группу (сейчас 5 % на каждого) | | Прочие мирные и нейтральные | нет | M7 | | | Враждебные мобы | частично | M7 | Зомби: 20 здоровья, скорость 0.23 (детёныш ×1.5, навсегда детёныш, `IsBaby`), рамка 0.6×1.95, глаза 1.74. Цель — ближайший игрок не в творческом (`NearestAttackableTargetGoal`: шанс 1/10 за тик, рамка 35 × 4 × 35, прямая видимость лучом по коробкам коллизии), забывается за 60 тиков без видимости и за 35 блоками; удар игрока делает его целью (`HurtByTargetGoal`). `MeleeAttackGoal`: идёт к цели, бьёт раз в 20 тиков, когда рамка с досягаемостью 0.828 задевает игрока и он виден; урон 3 со сложностью (лёгкая `min(d/2+1, d)`, сложная ×1.5), тип `minecraft:mob_attack`, `ENTITY_ATTACK` для плагинов, сообщение `death.attack.mob`, отбрасывание 0.4. С целью поднимает руки (флаг моба 4). Горит на солнце по `Mob.isSunBurnTick`: затемнение неба из угла солнца и погоды, яркость у глаз больше 0.5, бросок `nextFloat·30 < (яркость−0.4)·2`, видит небо, не в воде и не под дождём — огонь на 8 с. Звуки `entity.zombie.*` в категории `HOSTILE`, лут `entities/zombie` (редкий дроп — условие `random_chance_with_enchanted_bonus`, картошка печётся на горящем), опыт 5, детёныш 12. Спавн — строка «Заселение чанка и спавн по тику», `/summon`, яйцо и раздатчик. `CanBreakDoors`: при `finalizeSpawn` бросок `random < specialMultiplier × 0.1`, NBT `CanBreakDoors`; навигация считает закрытые деревянные двери проходимыми, а `BreakDoorGoal` на сложной сложности ломает их за 240 тиков (`block_destruction`, `levelEvent` 1019/1021/2001, обе половины двери). Плагинам — `Zombie`, `Creeper`, `Skeleton`, `Spider`, `Slime`. ИИ — селекторы целей (`GoalSelector`/`TargetSelector` с приоритетами и флагами, `registerGoals` каждого вида), ходьба — строка «Поиск пути мобов». Размеры, глаза и атрибуты всех видов — из `DefaultAttributes` и `EntityType` `server.jar`. **Крипер**: `SwellGoal` с 3 блоков, набухание до 30 тиков, взрыв мощностью 3 (заряженный ×2) без разрушения при выключенном `mob_griefing`, выпадение блоков 1/мощность, урон `minecraft:player_explosion` со сложностью, `death.attack.explosion.player`; поджиг огнивом и огненным зарядом; молния заряжает (`powered`); метаданные набухания, заряда и поджига; NBT `powered`, `Fuse`, `ExplosionRadius`, `ignited`. **Скелет**: `RangedBowAttackGoal` (радиус 15, интервал 40 тиков, на сложной 20, стрейф и отход), `RestrictSunGoal`/`FleeSunGoal` и обход солнца в навигации, горит на солнце; лук в руке (`set_equipment`), натяжение (флаг 8), стрела с владельцем-мобом, разброс `14 − 4 × сложность`, урон со сложностью, `death.attack.arrow` с убийцей. **Паук**: рамка 1.4×0.9, лазание по стене при упоре (`WallClimberNavigation`, флаг 16), `LeapAtTargetGoal` 0.4, днём на свету не берёт цель и бросает её. **Слизень** (`AbstractCubeMob`): размер 1–127 в метаданных 18 и NBT `Size`/`wasOnGround`, здоровье — квадрат размера, урон и опыт — размер, скорость `0.2 + 0.1 × размер`, рамка 0.52 × размер. Прыжки `CubeMobMoveControl` (задержка `nextInt(20) + 10`, втрое короче на цель), цели всплытия, случайного направления, прыжков и атаки (устаёт за 300 тиков), цель — игрок не дальше 4 блоков по высоте. Бьёт касанием (кроме размера 1), если игрок в досягаемости и виден; у игрока 10 тиков неуязвимости к ударам мобов (`invulnerableTime`: проходит лишь превышение). Звуки прыжка, шлепка, удара, урона и смерти (`_small` у маленького). При смерти размер больше 1 даёт 2–4 детей вдвое меньше со смещениями ванили, с именем и постоянством родителя (`SlimeSplitEvent`). Лут `entities/slime` с предикатом `type_specific/cube_mob`. Спавн: слизневый чанк (`seedSlimeChunk`, сверено с JDK) ниже y 40 с шансом 1/10, на болотах на высоте 51–69 по фазе луны и свету; размер 1, 2 или 4 (`specialMultiplier` без обжитости чанка и луны). Плагинам — `Slime` с `getSize`/`setSize`. Сверка с `server.jar` — `monster_probe` (фитиль 29–30 тиков и та же воронка, интервалы стрел 60/40 тиков, лазание по стене) и `mob_path_probe`. Нет: эндермена, ведьмы, лавового куба, зомби-жителя, утопленника и прочих видов; `AvoidEntityGoal` (крипер от кошек, скелет от волков); наездника-скелета на пауке и эффектов паука на сложной; облака эффектов заряженного крипера, черепа от заряженного взрыва, набухания от падения; превращения скелета в зимогора; снаряжения зомби и шанса его надеть, подкреплений, превращения в утопленника, атаки жителей, черепах и големов, ломания дверей, шлема от солнца. `/summon` с NBT у нас даёт скелету лук, в ванили — нет (`finalizeSpawn` пропускается) | | Экипировка мобов | частично | M7 | `Mob.populateDefaultEquipmentSlots` как в ванили: шанс брони `0.15 × specialMultiplier`, материал 0..=5, порядок `HEAD, CHEST, LEGS, FEET`, обрыв с шансом 0.25 (0.1 на сложной), три попытки апгрейда материала по 0.1087. Зомби со сложной сложностью в 5 % (иначе 1 %) берёт железный меч, копьё или лопату, скелет всегда с луком. 26.3 хранит снаряжение в NBT `equipment` (mainhand, offhand, feet, legs, chest, head) и `drop_chances` (только отличия от 0.085), старый формат `HandItems`/`ArmorItems`/`DropChances` читается как запасной. `set_equipment` уходит при спавне, загрузке из `entities/*.mca` и после `/data merge entity`. `/summon ` применяет NBT как `SummonCommand.createEntity` с `finalize = false`: `equipment`, `drop_chances`, `Health` (с обрезкой по максимуму), `CustomName`, `active_effects` (заменяет список), `IsBaby`/`Age` и `PersistenceRequired`; случайной экипировки при этом нет, а `/summon` без NBT вызывает `finalizeSpawn` и получает её. Смерть по `Mob.dropCustomDeathLoot`: шанс слота плюс `0.01 × Добыча`, без убийцы игроком выпадает только «сохраняемое» (`> 1`), дамагабельные предметы теряют прочность `maxDamage − random.nextInt(1 + random.nextInt(max(maxDamage − 3, 1)))`. Проверка — [`zombie_equipment_probe`](../crates/bots/examples/zombie_equipment_probe.rs); юнит-тесты `equipment_population_matches_vanilla`, `mob_nbt_keeps_equipment_and_drop_chances`, `mob_nbt_reads_legacy_hand_and_armor_items`. Затем `Mob.populateDefaultEquipmentEnchantments`: главная рука с шансом `0.25 × specialMultiplier`, броня — `0.5 × specialMultiplier`, стоимость `5 + below((int)(specialMultiplier × 17) + 1)` (`EnchantmentsByCostWithDifficulty`) и чары тега `#minecraft:on_mob_spawn_equipment` (`#non_treasure`) в его порядке; живая проба ищет зачарованный лук скелета. Нет: плагинам снаряжение не отдаётся | | Поиск пути мобов | частично | M7 | Перенос `WalkNodeEvaluator`, `PathFinder`, `GroundPathNavigation` и `MoveControl` (`crates/net/src/mobs/path.rs`): типы клеток (`PathType`) по состояниям из `server.jar` и штрафы, шаг на `step_height`, падение до `getMaxFallDistance`, диагонали, заборы, двери, огонь, лава и вода; A* с `maxVisitedNodes = follow_range × 16` и частичным путём; следование с срезом углов и застреванием, пересчёт пути, поворот не больше 90° за тик; стрейф (`MoveControl.strafe`); обход солнца (`avoidSun`); лазание паука (`WallClimberNavigation`). Сверка: `mob_path_probe` — зомби выходит из загона-«П», клетки как в `server.jar`, тест сверяет траекторию (отклонение меньше 0.02 блока). Двери: зомби с `CanBreakDoors` (`setCanOpenDoors`) считает закрытые деревянные двери проходимыми, `BreakDoorGoal` на сложной сложности ломает их за 240 тиков (`block_destruction`, `levelEvent` 1019/1021/2001, обе половины). Нет: плавающей и летающей навигации (`WaterBoundPathNavigation`, `FlyingPathNavigation`), `AmphibiousPathNavigation`, пересчёта пути по изменению блоков рядом | | Боссы | нет | M7 | Дракон, кристаллы на шипах и бой — Край уже генерируется; иссушитель после своего ритуала | | Снаряды кроме стрел и фейерверка арбалета | частично | M7 | Снежки, яйца и жемчуг бросаются игроком; яйца могут породить цыплят, жемчуг телепортирует владельца с уроном. Столкновения проверяются с блоками, игроками и живыми мобами; стрелы также ранят мобов. Взрывные и оседающие зелья летят и накладывают эффекты на игроков рядом с попаданием; прямое попадание даёт полную силу, остальным мгновенные эффекты масштабируются расстоянием. Нет облака оседающего зелья, трезубца и эффектов зелий по мобам | ## Мир, который тикает | Фича | Статус | Этап | Заметка | |---|---|---|---| | Случайные тики | частично | M7 | В активных секциях выбираются позиции с учётом `random_tick_speed`: растут культуры, саженцы, стебли, кактус, тростник, ламинария, незерские и пещерные лианы, бамбук; размножаются грибы, увлажняются или сохнут грядки, распространяются трава и мицелий, тают снег и лёд от блочного света, распадается листва, лава поджигает соседей. Медные сундуки окисляются отдельно; огонь живёт в очереди отложенных тиков. Будящий аметист (`BuddingAmethystBlock.randomTick`): шанс 1/5, случайная из шести граней; в воздухе или полной воде — малая почка, на почке того же `facing` — следующая стадия до друзы, `waterlogged` по воде-источнику. Редстоун-руда (`RedStoneOreBlock`): зажигается ударом в выживании, шагом (на земле, без приседа, блок под `floor(y-0.2)`) и кликом — с блоком в руке клик идёт дальше в установку, иначе `SUCCESS` и эхо клетки и соседа по грани; горящая гаснет на случайном тике (тикает только `lit=true`). Проба `crates/bots/examples/minerals_probe.rs` против `server.jar`: состояния руды и итог роста аметиста за 400 тиков при скорости 4096 совпадают; расходится только порядок пакетов блоков после клика с установкой. Остальные случайно тикающие блоки ещё не подключены. Окисление всей меди (`WeatheringCopper.changeOverTime`): 15 семейств `WeatheringCopper.NEXT_BY_BLOCK` — блок, резная, вырезанная с плитой и ступенями, дверь, люк, решётка, прутья, лампа, фонарь, цепь, громоотвод, статуя голема и сундук; шанс 0.05688889, соседи в манхэттенском радиусе 4 (моложе — стоп, иначе `((same+1)/(same+later+1))²`, ×0.75 у неокисленной), свойства блока переносятся. Выборка — как у ванили только в секциях с тикающими состояниями (`LevelChunkSection.isRandomlyTicking`), флаг состояния — из таблицы, а не по имени | | Рост растений и распад листвы | частично | M7 | Растут основные культуры, кактус, тростник, ламинария, плакучие, извилистые и пещерные лианы, бамбук; грибы распространяются. Саженцы используют те же настроенные фичи деревьев 26.3, что и генератор мира: стволы, кроны, корни и декораторы; ель и джунглевое дерево поддерживают 2×2, тёмный и бледный дуб требуют 2×2. Костная мука тратится при попытке, успех роста обычного саженца — 45%; висячий мангровый отросток взрослеет по `age`. Листва как `LeavesBlock`: `distance` пересчитывается отложенным тиком через 1 после смены соседа (минимум по шести соседям + 1, брёвна и `#prevents_nearby_leaf_decay` — 0), случайный тик есть только у распадающейся (`distance` 7 без `persistent`) — она роняет добычу и исчезает; сверено пробой `leaves_probe` с `server.jar`. Точный выбор вариантов дерева, данные блок-сущностей от декораторов (например, пчелиное гнездо) и обычные лианы ещё не сверены с ванилью. Глазоцвет (`EyeblossomBlock.tryChangingState`): в оверворлде раскрывается по таймлайну `day` (`gameplay/eyeblossom_open`: открыт с 12600 до 23401), в Аду и Краю не меняется; смена — `block_change`, след `trail` цвета вида, длинный звук от случайного тика и короткий от назначенного, а такие же цветки в ±3/±2/±3 получают тик через `⌊5d⌋..⌊10d⌋` — волна по поляне. Бледный сад ставит глазоцветам тик при генерации (`simple_block` с `schedule_tick`), сервер тикает их через 1 тик после появления чанка | | Почва и грядки | частично | M7 | Грядка увлажняется водой в радиусе 4 блоков или дождём; без воды влажность падает, а пустая сухая грядка становится землёй. Падение игрока или моба вытаптывает грядку и выбивает культуру. Трава и мицелий гибнут под непрозрачным блоком или без света и распространяются на соседнюю землю при достаточном освещении; `snowy` берётся по целевой клетке. Мгновенная реакция на изменение опоры и дополнительные превращения земли пока не подключены | | Огонь и поджог | готово | M7 | Огниво и огненный заряд ставят огонь или зажигают ТНТ; раздатчик с огнивом делает то же. Огонь поддерживается опорой или горючими соседями, стареет по отложенным тикам, распространяется на дерево, листву, шерсть и растения, поджигает ТНТ, гаснет без топлива и под дождём; огонь душ на почве душ не распространяется, на незераке и магме обычный огонь не гаснет сам. Лава может поджигать соседей через случайные тики. Огонь сохранённых чанков возобновляет тики при загрузке | | Урон среды сверх падения и пустоты | готово | M7 | Для игрока и мирных мобов проверяется объём тела: лава наносит урон и поджигает, вода у глаз расходует 300 тиков воздуха и после исчерпания душит, кактус и движение в зрелых сладких ягодах ранят, рыхлый снег накапливает до 140 тиков замерзания и затем наносит урон, магма обжигает при стоянии. Вода тушит огонь, воздух восстанавливается на суше, кожаная броня защищает от замерзания; правила `fire_damage`, `drowning_damage`, `freeze_damage` учитываются. Воздух и замерзание синхронизируются клиенту метаданными | | Цикл дня | готово | M5 | `advance_time`, `set_time`. Погода к этому не относится | | Погода | частично | M7 | `advance_weather` переключает ясную погоду, дождь и грозу по независимым таймерам дождя и грозы; состояние отправляется вошедшему игроку и при смене. Тип осадков берётся из `temperature` и `has_precipitation` биома 26.3: в пустыне осадков нет, в холоде идёт снег и замерзает открытая вода, дождь наполняет котлы. Снег и лёд тают от блочного света. Гроза создаёт молнию с визуальной сущностью, звуком, уроном и разрядом молниеотвода. Уровни дождя и грозы меняются по 0,01 за тик и отправляются клиенту. Высотный температурный шум и точная частота выпадения снега ещё не перенесены | | Сон и точка возрождения с кровати | готово | M7 | В обычном мире клик по любой половине целой кровати назначает точку возрождения; ночью игрок ложится, обе половины получают `occupied`, поза и положение рассылаются клиентам. `players_sleeping_percentage` считает игроков вне наблюдения; при пороге наступает утро, погода очищается, все просыпаются. Действие, урон, выход и разрушение кровати будят игрока. Респаун ищет место с опорой и свободным пространством у целой кровати, иначе использует спавн мира. В Аду и Краю (`bed_rule` с `destroy_on_use`) клик по кровати убирает обе половины и взрывает изголовье силой 5 с огнём (`createFire`: треть пустых клеток над твёрдым). Точка возрождения хранит измерение и взгляд (`RespawnConfig`) и пишется в сохранение как ванильный `respawn` (`dimension`, `pos`, `yaw`, `pitch`, `forced`); старые `SpawnX`/`SpawnY`/`SpawnZ` читаются, без `SpawnForced` точка принудительная. Возрождение (`findRespawnPositionAndUseSpawnBlock`): якорь в Аду с зарядом — место по `findStandUpPosition` (25 соседних клеток, `DismountHelper` по коробкам `shapes.csv`, сначала без опасных блоков), заряд −1, взгляд на якорь, звук `respawn_anchor.deplete` только самому игроку; кровать — только в оверворлде; `/spawnpoint` — клетка +0.5/+0.1/+0.5 с сохранённым углом, если в ней и над ней не твёрдое и не жидкость. Пропавшая точка — `game_event` 0, спавн мира, точка стирается. Игрок возрождается в измерении точки. `/spawnpoint [цели] [pos] [rotation]`: без позиции — блок цели, измерение источника, сообщение с 7 аргументами. Сверено ботом `respawn_probe` с `server.jar`: взрыв якоря и кровати, точка якоря, повторный клик, смерть у якоря (Ад, точка, угол −180, звук), пустой якорь (`game_event` 0), `/spawnpoint` с углом. Расходится выбор места на спавне мира (у ванили `spawn_radius`-поиск безопасной клетки) и порядок пакетов звука и блока после клика. | | Порталы в Ад и Край | готово | M7 | Портал в Ад: `PortalShape` по байткоду (`find_portal` в [crates/net/src/redstone.rs](../crates/net/src/redstone.rs)), рамка 2…21 × 3…21, огонь зажигает `nether_portal`. Переход: координаты /8 и ×8, поиск портала радиусом 128 в Аду и 16 в оверворлде, новая рамка 2×3 на площадке по всей высоте измерения, `PortalCooldown` 300 тиков. Портал в Край: глаз Края в рамку (`EnderEyeItem.useOn`, проверка кольца 3×3 и заполнение `end_portal`), бросок глаза к ближайшей крепости, переход на платформу 5×5 в (100, 49, 0), выход — к точке возрождения, титры один раз по `seenCredits`. Шлюз `end_gateway` ведёт по `exit_portal`. После `respawn` поток чанков начинается заново в новом мире (`stream.restart`), иначе общие с прежним центром чанки не приходили | ## Станции и рецепты В `crates/data/src/recipes.rs` есть `crafting_shaped`, `crafting_shapeless`, три готовки печи и `campfire_cooking`. Звук блока и его неподвижность для поршня механикой не считаются. | Фича | Статус | Этап | Заметка | |---|---|---|---| | Костёр | готово | M7 | Готовка на костре и костре душ: 4 слота, 600 тиков, остывание без огня, `CampfireStartEvent`, `BlockCookEvent`. Тушение, урон и дым — нет | | Наковальня | готово | M7 | Починка материалами и соединением предметов, перенос совместимых чар с книг и вещей, переименование, стоимость опыта с учётом прошлых работ и лимита, износ наковальни; входы возвращаются при закрытии | | Кузнечный стол | готово | M7 | Рецепты `smithing_transform` и `smithing_trim` из 26.3 с ингредиентами по тегам; улучшение сохраняет компоненты основы, отделка записывает материал и узор в `minecraft:trim`; результат рассчитывается сервером, три входа расходуются по одному, Shift-перенос и возврат входов при закрытии | | Точило | готово | M7 | Снятие чар кроме проклятий, починка двух одинаковых, книга → книга, сферы опыта, `PrepareGrindstoneEvent`. Отделка и сырой патч не кладутся | | Камнерез | готово | M7 | Рецепты 26.3, включая теги и количество результата; выбор результата, расход одного входа, Shift-перенос и возврат входа при закрытии | | Ткацкий станок | готово | M7 | Окно и выбор узоров 26.3, включая шаблоны; до шести слоёв на знамени, расход знамени и красителя, Shift-перенос, возврат входов при закрытии. Узоры сохраняются в компоненте предмета и playerdata | | Карты | частично | M7 | Пустая карта (`EmptyMapItem.use`): номер из `data/minecraft/maps/last_id.dat` (`MapIndex`), центр по сетке `128·2^scale`, звук `ui.cartography_table.take_result`, карта в руку или в инвентарь, иначе на землю. В руке рисует по `MapItem.update` из байткода: столбцы `step & 15` и подряд, пока соседний менялся; высота сверху вниз до первого цвета, глубина жидкости, `getCorrectStateForFluidBlock` (верх не держит — видна вода или лава), самый частый цвет квадрата `scale×scale`, яркость по уклону и шахматке, потолок Ада — хэш земли и камня, незагруженный чанк пропускается. Цвет состояния — `MapColor.id` из выгрузки `ChunkwakeMapColors` (`reports/map_colors.csv`, кэш v11). Значки держателей (`tickCarriedBy`): игрок на карте, у края (`player_off_map`) и за пределами (`player_off_limits`), вращение в Аду от времени. Заплатки и значки — `map_item_data` по `HoldingPlayer.nextUpdatePacket` (значки раз в 5). Компонент `map_id` в сети, NBT и строке плагинов. Запись `.dat` (`data`, `DataVersion`) и `last_id.dat` в автосохранении, чтение при первом обращении. Сверка с `server.jar` (сид 12345, карта у (8, 8), бот `map_probe`): 16371 из 16384 пикселей совпадают, остальные 13 — местность (тёмный дуб на стыке чанков стоит иначе, лава натекла за дольший прогон). Карта в рамке раз в 10 тиков рассылается всем игрокам измерения и рисует значок рамки (`frame-`, поворот по стороне); значки рамок пишутся в `frames` и переживают перезапуск, снятая карта теряет значок. Знамёна (`MapItem.useOn` → `toggleBanner`): заполненная карта по знамени ставит значок `banner_<цвет>` с именем знамени, повторный клик снимает; `checkBanners` в каждом перерисованном столбце снимает значок, когда знамени нет или оно другое; метки пишутся в `banners` (`pos`, `color`, `name`) и переживают перезапуск. Сверка с `server.jar` (бот `banner_probe`): значки, имена и снятие совпадают. Вырезанная тыква на голове скрывает значок игрока на карте в руке и в рамке (`map_invisibility_equipment`). Нет: карт сокровищ и исследователя | | Рамки для предметов | частично | M7 | Обычная и светящаяся рамка (`ItemFrameItem.useOn`): на любую грань в пределах высоты мира, опора — `isSolid` (столбец в `light.csv`, кэш v12) или повторитель и компаратор у стены, без коллизии в коробке и без другой рамки той же стороны. Проверка опоры раз в 100 тиков, без неё рамка падает с предметом. Клик кладёт один предмет, следующие поворачивают (8 положений), первый удар снимает предмет, второй ломает рамку; в творческом ничего не падает, закреплённую (`Fixed`) ломает только творческий. Взрыв ломает целиком, `entity_drops` учитывается, защита спауна тоже. Данные сущности — только изменённые поля (`packDirty`), звуки `item_frame.*` и `glow_item_frame.*`. Сохраняются в `entities/*.mca` (`block_pos`, `Facing`, `Item`, `ItemRotation`, `ItemDropChance`, `Invisible`, `Fixed`, `CustomName`). Сверка ботом `frame_probe` с `server.jar`: установка, вкладывание, поворот, удар, поломка, сигнал компаратора и значок карты в светящейся рамке совпадают. Карта не вставляется в пустую рамку, если на ней уже 256 отслеживаемых значков. Нет: стрел и снарядов по рамке, данных сущности из компонентов предмета (`entity_data`) | | Стол картографа | готово | M7 | `CartographyTableMenu`: вход — заполненная карта, второй — бумага (увеличить, если не заперта и масштаб < 4), стекло (запереть) или пустая карта (2 копии). Результат несёт `map_post_processing`, клиент рисует превью; забранная стопка получает новый номер (`MapItem.onCraftedBy`: `scaled` — пустая карта крупнее с тем же центром, `locked` — копия пикселей). По штуке из обоих входов, звук не чаще раза в тик, шифт-клик как `quickMoveStack`, входы возвращаются при закрытии. Живой прогон против `server.jar`: те же пакеты (копия, запертая — номер 1, увеличенная — номер 2), запертая после перезапуска приходит с диска | | Варочная стойка | готово | M7 | 279 рецептов `minecraft:brewing` из данных 26.3: обычные, взрывные и оседающие зелья. Окно из пяти слотов, Shift-перенос, 400 тиков варки, 20 применений на огненный порошок, остаток реагента, сохранение `Items`/`BrewTime`/`Fuel`, положение бутылок в состоянии блока и правила воронок по граням; компаратор считает заполненность | | Зелья на мобах | частично | M7 | Брошенные `splash_potion`/`lingering_potion` действуют на мобов в радиусе 4 с силой `1 − d/4`: `instant_health`/`instant_damage` лечат или ранят (у нежити — наоборот), яд, иссушение и регенерация тикают (`25 >> amp`, `40 >> amp`, `50 >> amp`), Скорость/Замедление меняют `Mob.speed` (сумма `+20%`/`−15%` за уровень), Слабость снимает 4 урона за уровень; `active_effects` виден в `data get` и сохраняется в `entities/*.mca`. `/item replace` теперь применяет `potion_contents` (раньше терял). Оседающее зелье создаёт `AreaEffectCloud`: радиус 3 блока, 10 тиков ожидания, 600 тиков жизни, эффекты каждые 5 тиков (повторно одной цели — через 20), радиус убывает на `3/600` за тик и облако исчезает при `< 0.5`; действует и на игроков, и на мобов. Проверки — [`mob_potions_probe`](../crates/bots/examples/mob_potions_probe.rs), [`lingering_cloud_probe`](../crates/bots/examples/lingering_cloud_probe.rs), [`jump_boost_probe`](../crates/bots/examples/jump_boost_probe.rs). Прыгучесть: `SAFE_FALL_DISTANCE` +1 за уровень у игрока и моба (`safe_fall_distance`), прыжок моба `0.42 + 0.1 × (уровень + 1)`; у игрока высоту добавляет клиентский `getJumpBoostPower`. Частицы эффектов — `DATA_EFFECT_PARTICLES` (10) и `DATA_EFFECT_AMBIENCE_ID` (11) в `set_entity_data` с цветами `MobEffect` и альфой 38 у всех ambient; клиент рисует их сам, проба [`effect_particles_probe`](../crates/bots/examples/effect_particles_probe.rs) | ## Игрок и команды | Фича | Статус | Этап | Заметка | |---|---|---|---| | Элитры | готово | M5 | `START_FALL_FLYING`, если в нагруднике элитры и игрок не в креативном полёте; `canGlideUsing` — на последней единице прочности полёт гаснет, элитры целы. Износ `updateFallFlying`: единица раз в 20 тиков полёта (с Прочностью — шанс `1/(L+1)`, элитры не в `#enchantable/armor`), не в креативе. Ракета в полёте (`FireworkRocketItem.use`): сущность с `DATA_ATTACHED_TO_TARGET` (индекс 9, `OPTIONAL_UNSIGNED_INT` = 19), разгон считает клиент игрока, срок `10 × (1 + flight_duration) + nextInt(6) + nextInt(7)` из компонента `fireworks`, звук запуска `AMBIENT` 3.0, тратится не в креативе; ракета со звёздами ранит своего игрока на `5 + 2 × взрывов`. Взрыв любой ракеты — событие сущности 17 (было 3, клиент не показывал взрыв). Не перенесено: урон взрыва соседям в радиусе 5, повод смерти `fireworks`, частицы звёзд у ракет арбалета и раздатчика (компоненты не передаются в сущность) | | Защита спавна | готово | M5 | Радиус из `server.properties`. Не-оператор не ломает и не ставит внутри (`spawn_blocked`) | | Команды ванили | частично | M5 | Дерево объявлено в [`crates/net/src/play/commands/registry.rs`](../crates/net/src/play/commands/registry.rs), права сверяются с ванильным `commands.json` тестом. Есть `help`, `list`, `seed`, `version`, `say`, `me`, `msg`/`tell`/`w`, `teammsg`/`tm`, `tellraw`, `title`, `playsound`, `stopsound`, `give`, `clear`, `effect`, `xp`, `gamemode`, `defaultgamemode`, `kill`, `tp`/`teleport` (игроки, мобы и выпавшие предметы через `teleport_entity`; `@s` работает и для мобов), `damage`, `rotate`, `spawnpoint`, `setblock`, `fill`, `summon`, `weather`, `gamerule`, `time`, `difficulty`, `setworldspawn`, `forceload`, `scoreboard` (цели и значения), `data get`, `execute` (as/at/positioned/rotated/facing/in/if/unless/run), `op`/`deop`, `kick`, `whitelist`, `ban`/`ban-ip`/`banlist`/`pardon`/`pardon-ip`, `reload`, `tick`, `debug`, `perf`, `stop`, `save-all`/`save-off`/`save-on`, `publish`/`unpublish`, `transfer`, `setidletimeout`. Расширения: `tps`, `top`. Хвост (`advancement`, `attribute`, `bossbar`, `clone`, `team`, `worldborder` и остальные) — [docs/commands.md](commands.md) | | Пакеты клиента | частично | M7 | Тест [`packets_parity.rs`](../crates/net/tests/packets_parity.rs) сверяет serverbound play-пакеты из `reports/packets.json` с таблицей `sb("…")` в `play.rs`. Обработано 37 из 69, остальные 32 перечислены в тесте явно (`UNHANDLED`). `pick_item_from_entity`: средняя кнопка выбирает яйцо призыва моба, лодку, вагонетку или предмет в рамке; расстояние считается от глаз до коробки сущности с атрибутом `entity_interaction_range`. Предмет из рамки выбирается с компонентами; копирование профиля Avatar пока отсутствует. F3+F4 (`change_game_mode`) меняет режим при правах `/gamemode` (уровень 2), при повторе ничего не делает. Из необработанных заметны в игре: `entity_tag_query`/`block_entity_tag_query` (F3+I), `teleport_to_entity` (наблюдатель), `place_recipe`, `select_trade`, `seen_advancements`. `move_player_status_only` обрабатывается как движение без позиции и поворота: стоящий игрок приземляется, урон от падения и флаг `on_ground` считаются. `client_information` (конфигурация и play, `handleClientInformation`): обзор `clamp(запрос, 2, обзор сервера)` с выгрузкой и догрузкой кольца при смене, рука и части скина в метаданных (индексы 15/16, только изменённые, следящим и себе; при спавне — только не по умолчанию), шляпа в таб-листе (`UPDATE_HAT` при входе и при смене). Байты сверены с `server.jar` пробой [`client_info_probe`](../crates/bots/examples/client_info_probe.rs); боты шлют `client_information` в конфигурации (обзор из `CHUNKWAKE_BOT_VIEW`, по умолчанию 32). Чанки игрока — круг ванили (`ChunkTrackingView.isWithinDistance` с каймой 2, `isChunkTracked`): отправка, видимость сущностей и рассылка блоков; 117 чанков при обзоре 4 и 49 при 2, как у `server.jar`. `chunk_batch_received` и темп отправки — `PlayerChunkSender`: готовые чанки копятся в очереди и уходят раз в тик ближними первыми, квота растёт на `desiredChunksPerTick` (9 до первого ответа, потом `clamp(ответ, 0.01, 64)`), в полёте 1 пачка до первого ответа и 10 после; уход из обзора снимает неотправленный чанк без `forget_level_chunk`, кадр берётся в момент отправки. Юнит-тесты `play::stream::tests` с числами ванили, проба [`batch_probe`](../crates/bots/examples/batch_probe.rs): у ванили и у нас 117 чанков пачками по готовности (ванила 9, 59, 13, 28, 8; у нас 1, 64, 52 — у ванили к первому тику готовы 9). `isChunkTracked`: игроки и рассылка блоков — только в чанках, уже отправленных клиенту; игрок в только что пришедшем чанке появляется тиком позже, как у ванили (проба [`track_probe`](../crates/bots/examples/track_probe.rs)). Дальность слежения `ChunkMap.TrackedEntity` для мобов, предметов, сфер опыта, лодок, вагонеток и рамок: `min(обзор × 16, clientTrackingRange × 16)` с учётом всадника, из выгрузки `ChunkwakeEntities` (`reports/entities.csv`); вошла — спавн, вышла — `remove_entities`, пакеты о сущности только видящим. Проба [`entity_range_probe`](../crates/bots/examples/entity_range_probe.rs) при обзоре 10: 473 чанка, зомби ≤ 128, предметы ≤ 96, коровы и овцы ≤ 160, как у `server.jar`. Не сделано: снаряды, TNT, молнии и око Эндера пока уходят всем в мире; ваниль шлёт часть сущностей раньше их чанка (чанк загружен, но ещё не готов к отправке), у нас — всегда после | | Modern Velocity forwarding | готово | M5 | `velocity-enabled=true` и `velocity-secret` в `server.properties`, `online-mode=false`. Сервер отправляет `LoginPluginRequest` по каналу `velocity:player_info`, проверяет HMAC-SHA256, читает версию 1–4, UUID, имя и свойства; в Mojang не ходит. Клиент без прокси получает отказ. Legacy/BungeeCord не поддерживается. Тесты `velocity.rs` (HMAC и разбор) и `velocity_login.rs` (логин через поддельный прокси). Проверено с настоящим Velocity 4.3.1: бот вошёл через прокси, backend получил offline-UUID и имя (`velocity_probe`) | | Взмах руки | частично | M7 | `punch` (`ServerGamePacketListenerImpl.handlePunch`): `attack_animation` предмета в основной руке (по умолчанию `whack` 6, у копий `stab` 13–23), `SwingState.startIfAble` (новый взмах — после половины текущего или в тот же тик), длительность с Спешкой/Силой источника и Утомлением (`getModifiedSwingDuration`). `swing_animation` получают остальные игроки, сам игрок — нет. Успешные `use_item`, `use_item_on` и взаимодействие с сущностями взмахивают (`swingAndResetAttackStrength`) и сбрасывают заряд. Сверено ботом `swing_probe` с `server.jar`: 4 `punch` по расписанию дают 3 пакета у наблюдателя и 0 у игрока, вид и длительность совпадают. | | Загрузка клиента | готово | M5 | `ServerGamePacketListenerImpl.hasClientLoaded`: до `player_loaded` (или 60 тиков после входа/респауна) движение, атака, `punch`, `use_item`/`use_item_on`, `interact`, `player_command` и `move_vehicle` игнорируются; урон тоже не проходит. `player_loaded`, `accept_teleportation` и `keep_alive` обрабатываются всегда. Проба [`load_probe`](../crates/bots/examples/load_probe.rs): `DROP_ITEM` до загрузки не создаёт предмет, после — создаёт | | Бой игрока | частично | M5 | Урон предмета и Остроты умножается на `0.2 + scale²·0.8`, где `scale` — `getAttackStrengthScale(0.5)` по `attack_speed` (тикер растёт каждый тик, сбрасывается ударом, взмахом `punch` и сменой предмета); крит ×1.5 только при `scale > 0.9`, падении (`fallDistance > 0`) и отсутствии спринта, воды, лиан и Слепоты; отбрасывание `0.4` + `0.25·Отдача` + `0.5` за удар с бега (бег гаснет); звуки `weak`/`strong`/`crit` и `animate`-крит, плюс `animate` 2 при бонусе чар. Разящий удар мечом по `#minecraft:swords`: цель — все в AABB цели +1, урон `1 + sweeping_damage_ratio × item_damage + Острота`, умноженный на заряд; `sweeping_damage_ratio` даёт `уровень / (уровень + 1)` (Sweeping Edge из данных 26.3), звук и частицы `sweep_attack`. Лодки и вагонетки получают тот же заряд. Проверка — [`attack_cooldown_probe`](../crates/bots/examples/attack_cooldown_probe.rs) (включая Sweeping Edge 3, сброс заряда постановкой блока и снежком из второй руки). Заряд сбрасывают успешные постановка блока (`use_item_on`), выброс предмета (`ServerPlayer.drop`), `use_item` (вёдра, книги, бутылки, жемчуг, лодки, карты, фейерверк, броски), клики по дверям/рычагам/сундукам и взаимодействие с сущностями — взмах `swing_animation` уходит зрителям, `resetAttackStrengthTicker` срабатывает, если `SwingState` разрешил (`swingAndResetAttackStrength`). Нет: булавы (`MaceItem`), копий (`kinetic_weapon`, `attack_range`), щита, тотема, боя игрок против игрока | ## Не начато Аудит 2026-11-08: этих механик нет в коде (поиск по `crates/net/src` пуст или находит только имена в списках), а строк в матрице до сих пор не было. | Фича | Статус | Этап | Заметка | |---|---|---|---| | Таблички и книги | частично | M7 | Таблички и подвесные таблички: блок-сущность `sign`/`hanging_sign` (`front_text`/`back_text`, `color`, `has_glowing_text`, `filtered_messages`, `is_waxed`) с текстом в чанке и в `block_entity_data`. Редактор открывается после установки (`PLACE`) и по клику (`INTERACT`) с той стороны, к которой стоит игрок (`getSlotPlayerIsFacing`, центр настенной — у стены); правит только тот, кому открыт (`playerWhoMayEdit`, забывается, если отошёл дальше руки + 4). `sign_update`: обрезка до 80 code points и снятие кодов `§` (Paper), стиль прежней строки сохраняется, чужая правка — откат текста клиенту в радиусе 32. Краситель, чернила, светящиеся чернила (только с текстом), соты (событие мира 3003, звук) и звук отказа у вощёной; предмет списывается не в творческом. События `UncheckedSignChangeEvent`, `SignChangeEvent` (отмена и новые строки), `PlayerOpenSignEvent` и `PlayerSignOpenEvent`; `Block.getState()` → `Sign`/`HangingSign` со сторонами, цветом, свечением, воском и `update()`, `Player.openSign`. Тесты `signs.rs`, `SignsTest`. Книги: компоненты `writable_book_content`/`written_book_content` разобраны (провод, диск — страницы подписанной JSON строкой в `{raw}`, мост). `edit_book` (`handleEditBook`): слот хотбара или второй руки, проверка размера Paper (`book-size` 2560 × 0.98, кик «Book too large!») и не чаще раза в 20 тиков (кик «Book edited too quickly!»), черновик — страницы, подпись — `written_book` с автором, поколением 0 и `resolved`. `PlayerEditBookEvent` (отмена, новая мета, снятие подписи); `BookMeta`/`WritableBookMeta` у книжных стопок, `ItemFactory` (`getItemMeta`, `equals`, `asMetaFor`). Подписанная книга открывается по ПКМ (`open_book`) и помечается `resolved`. Тесты `books.rs`, `BooksTest`. Нет: команды `click_event` у вощёных табличек, `allow_op_features`; разрешение селекторов, счёта и NBT в страницах (`resolveForItem` только ставит флаг); фильтр текста; неизвестные компоненты книги теряются при правке (сырой патч) | | Маяк и проводник | частично | M7 | Маяк: пирамида из `#minecraft:beacon_base_blocks` (3×3…9×9, уровень 1–4), окно `minecraft:beacon` (`BeaconMenu`: слот оплаты, `container_set_data` уровня и эффектов), `set_beacon`, оплата из `#minecraft:beacon_payment_items` ровно одним предметом. Эффекты раз в 80 тиков в радиусе `10 + 10·уровень` на `(9 + 2·уровень)·20` тиков; на 4-м уровне второй эффект, одинаковая пара даёт II, регенерация только второй. NBT `primary_effect`/`secondary_effect`/`Levels`/`CustomName`; `Levels` уходит клиенту в `block_entity_data`, по нему рисуется луч. Проба `beacon_probe`: уровень 1 (скорость, 220 тиков), уровень 4 (скорость II, 340 тиков), загрузка из NBT после перезапуска. Проводник — каркас из призмарина, Сила источника, атака мобов — не начат | | Стойка для брони | готово | M7 | `minecraft:armor_stand`: постановка на блок (не вниз) с проверкой коллизий, ближайшие 45° от игрока. Снаряжение — шесть слотов `set_equipment` (руки, ступни, ноги, грудь, голова), надевание по `equippable.slot`, снятие пустой рукой по высоте клика, обмен, `DisabledSlots` (брать/снимать/класть) и `ShowArms`. NBT: `ArmorItems`, `HandItems`, `Invisible`, `Small`, `ShowArms`, `DisabledSlots`, `NoBasePlate`, `Marker`, `Pose`; позы уходят метаданными (индексы 16–21, `ROTATIONS`). Удар: первый — толчок и звук, второй в течение 5 тиков (или любой в творческом) ломает; выпадают стойка и снаряжение. Взрыв ломает с дропом. Проба `armor_stand_probe`: постановка, надевание шлема, два удара, дроп | | Рыбалка, трезубец, щит, тотем | нет | M7 | `FishingHook` и лут `gameplay/fishing`, бросок трезубца (Верность, Тягун, Громовержец), блок щитом с перезарядкой от топора, тотем бессмертия | | Жители, торговля, рейды | нет | M7 | `Brain` и профессии по точкам интереса, `select_trade`, рейды и разбойники | | Статистика, достижения, книга рецептов | нет | M7 | `award_stats`, `stats/.json`, `advancements/.json` и триггеры, `/advancement`, `/recipe`, `place_recipe` | | Общая система `gameEvent` | нет | M7 | Сейчас `emit_vibration` обходит куб 17³ и активирует сенсор сразу. Нет `GameEventListenerRegistry` по секциям, задержки вибрации на `distance` тиков, затенения шерстью (`#occludes_vibration_signals`), частицы `vibration`, выбора ближайшей вибрации за тик; на этом стоят катализатор, визгун и страж | ## Плагины (Paper API) `paper-api.jar` целиком лежит в classpath JVM: классы не переписаны и не сгенерированы, реализуется только поведение. Недостающий метод сервера возвращает пустое значение и **один раз** пишется в журнал как `api: <интерфейс>.<метод> is not implemented` — список того, что стоит закрыть, виден без отладки. Строгий режим (исключение вместо пустого значения) — `CHUNKWAKE_API_STRICT=1`. | Фича | Статус | Заметка | |---|---|---| | API встроен в бинарь | готово | `java/dist` (48 jar'ов, `crates/bridge/build.rs`) упаковывается в zip, встраивается в exe и разворачивается в `<каталог сервера>/api` при первом старте: релиз — **один файл**, рядом ничего раскладывать не нужно. Отпечаток архива в `.chunkwake-api-bundle`: после пересборки API заменяется, до тех пор второй запуск ничего не пишет. Сборка без встраивания — `CHUNKWAKE_API_BUNDLE=none`. Проверено на чистом каталоге: только `chunkwake.exe` + плагин → `enabled 1 plugin(s)`, `doctor` — `Ready to start` | | Библиотеки из `plugin.yml` | готово | Секция `libraries:` резолвится `MavenLibraryResolver` из paper-api и попадает в classpath плагина до создания main-класса; скачанное кэшируется в `libraries/` рядом с сервером. Без этого BlueMap не стартовал: `com.flowpowered:flow-math` (см. `Libraries.java`) | | Сервисы paper-api (`ServiceLoader`) | готово | 12 провайдеров: 5 своих (`InternalAPIBridge`, `ServerBuildInfo`, `RegistryAccess`, `PluginLoader`, `LifecycleEventTypeProvider`) и 7 сгенерированных задачей `:api-impl:generateProviders` из `java/tools/GenProviders.java`. 8 из них объявлены package-private — реализовать их можно только классом в пакете paper-api, поэтому руками не пишутся: генератор читает paper-api.jar и выдаёт `Chunkwake<Имя>` плюс `META-INF/services`. При смене версии paper-api всё перегенерируется | | События жизненного цикла | частично | `LifecycleEvents.COMMANDS` диспетчеризуется сразу при подписке: команды плагина попадают в `CommandMap` и играются (`/bluemap` виден в игре). `DATAPACK_DISCOVERY` отдаёт `DatapackRegistrar`: из пака плагина (каталог или ресурс jar) читаются только теги блоков и предметов. Теговые события (`TAGS`) регистрируются, но не наступают | | `paper-plugin.yml`: `loader` и `bootstrapper` | готово | `PluginLoader.classloader` — библиотеки из Maven (`MavenLibraryResolver`) и `ClassPathLibrary` до создания плагина; `PluginBootstrap.bootstrap(BootstrapContext)` и `createPlugin`. Без этого Veinminer падал на `KPaper` | | Зависимости между плагинами | готово | Порядок загрузки и включения — по `depend`/`softdepend`/`loadbefore` (`PluginOrder`), `provides` засчитывается; без обязательной зависимости плагин не грузится, как в Paper. Классы общие (`PluginClassGroup`, глобальная группа Paper для `plugin.yml`): чего нет у родителя и в своём jar, ищется у других плагинов — сначала у зависимостей; доступ к плагину не из зависимостей пишется в журнал. Загрузчики параллельные: взаимные ссылки из разных потоков не встают | | Команды Paper | готово | Как в Paper 26.3 (вывод сверен с байткодом `PaperPluginsCommand`/`PaperVersionCommand`/`PaperMSPTCommand`): `/plugins` (`/pl`, секции Paper/Bukkit, зелёный — включён, красный — выключен, `*` — legacy, клик → `/version`), `/version` (`/ver`, `/about`, с плагином — версия, сайт, авторы; поиск по части имени), `/mspt` (avg/min/max за 5 с, 10 с, 1 мин), `/help` (темы по всем командам, алиасы, индекс по плагину), `/bukkit:reload [confirm]` (выключает плагины, закрывает загрузчики и грузит каталог заново; отказ, если плагин зарегистрировал lifecycle-обработчик, — как Paper). Права `bukkit.command.*` с default Bukkit, `ServerLoadEvent` STARTUP/RELOAD. Команда плагина перекрывает ванильную с тем же именем, ванильная доступна как `minecraft:<имя>`. Дерево у клиента — по default права команды (оператор/все/не-операторы); права, выданные плагином (LuckPerms), дерево пока не учитывает. `Server.getTPS`/`getAverageTickTime` — из окна тиков Rust | | Реестры Bukkit/Paper | готово | Все 44 `RegistryKey` paper-api заполнены ванильными ключами. Rust отдаёт их кусками по запросу `registry <имя> <с какой>`: встроенные реестры — по ID из `registries.json`, data-driven — в сетевом порядке, `worldgen/structure` — из файлов данных. Значение строится по типу (`RegistryValues`). Интерфейсы (`Attribute`, `Sound`, `Biome`, варианты мобов…) — прокси с `name()`/`ordinal()` как у Paper `OldEnumHolderable` и ванильным ключом перевода. Enum (`EntityType`, `Particle`, `PotionType`) — их константы. Для абстрактных классов — свои подклассы: `GameEvent` (радиус 16/10/32, частота вибрации по `VibrationSystem`), `Structure` (тип из файла), `StructureType`, `MusicInstrument` (звук, длительность, дальность), `GameRule` (тип и умолчание из таблицы `/gamerule`; `legacyGameRuleBridge` для старых констант). `Attribute.getDefaultValue` — ванильная база, `getSentiment` — по `Attributes`. `BlockType` и `ItemType` отдаются как `Typed`, `Biome.CUSTOM` — как у Paper. `World.getGameRuleValue`/`setGameRule`/`getGameRules` читают и пишут те же правила, что `/gamerule`. Тест `RegistriesTest`: на ванильных данных инициализируются все классы-держатели констант, ни одна константа не `null`. Живой прогон: Veinminer получает `BlockBreakEvent` без падения на `Attribute`. Не сделано: данные записей помимо ключей (цвета вариантов, свойства биомов и т. п. — мягкие умолчания) | | Атрибуты сущностей для плагинов | готово | `Player.getAttribute` и `getAttribute` мобов отдают `AttributeInstance` поверх запроса `attr <атрибут> get|base|reset|add|remove`. База, модификаторы и пределы — те же, что у `/attribute`: видны модификаторы эффектов, чар, команд и плагинов, итог считается по ванильной формуле, клиент получает `update_attributes`. Атрибута нет у сущности — `null`, как у ванилы. Повторный `addModifier` с тем же ключом — `IllegalArgumentException`, как у Paper. Тест `AttributeInstanceTest`. Живой прогон: Veinminer при ломании блока снимает свой модификатор без ошибок. Не сделано: временные модификаторы хранятся как обычные, сохранения атрибутов между входами нет | | Планировщики Folia | готово | `getGlobalRegionScheduler`, `getRegionScheduler`, `getAsyncScheduler` и `Entity.getScheduler` ведут себя как у Paper на обычном сервере (`FoliaGlobalRegionScheduler`, `FallbackRegionScheduler`, `FoliaAsyncScheduler`, `FoliaEntityScheduler`): те же проверки задержек и периода, `IllegalPluginAccessException` для выключенного плагина, машина состояний `ScheduledTask` (`cancel` изнутри повторяющейся задачи — `NEXT_RUNS_CANCELLED`). Регион один — главный тик плагинов; тиковые задачи Folia не видны через `BukkitScheduler`. Асинхронные — таймер плюс виртуальные потоки. Задачи сущности после её исчезновения не выполняются, вместо них один раз вызывается `retired`. `isOwnedByCurrentRegion`/`isGlobalTickThread` — `true` на главном потоке плагинов. При выключении плагина его задачи отменяются. Тест `FoliaSchedulersTest`. Живой прогон: тестовый плагин с `folia-supported: true` — все четыре планировщика выполнили задачи. Не сделано: настоящей многопоточности регионов нет | | Реестры ванили в JVM (NMS) | частично | Классы `net.minecraft.*` из `server.jar` (`api/game-classes.jar` и `game-libs`) лежат в classpath JVM плагинов. Ванильный `Bootstrap.bootStrap()` (`VanillaBootstrap`) идёт в своём потоке сразу после старта JVM, параллельно с Via; загрузка плагинов ждёт его, как на Paper. Подменённые бутстрапом `System.out`/`err` возвращаются обратно. Занимает около 2,7 с и 80 МБ кучи; без jar игры пропускается. Живой прогон: commonnetworking включается без «Not bootstrapped». Не сделано: `MinecraftServer.getServer()` (Veinminer), `CraftServer.getServer` (voicechat) — ванильного сервера в JVM нет; нужны шимы | | Теги для плагинов | готово | `Server.getTag` для `blocks`/`items` (`Material`): ванильные теги из Rust (`tag block|item имя` в `world_request`, вложенные раскрыты) плюс теги датапаков плагинов; файл без `replace` дополняет тег, как в ванили. Veinminer принимает `#c:ores` и `#minecraft:pickaxes`. Нет `getTags` и тегов других реестров | | Каналы плагинов | готово | `Messenger` — `StandardMessenger` из Bukkit. `custom_payload` от клиента идёт событием в мост: `minecraft:register`/`unregister` (и из configuration, после входа) — каналы игрока и `PlayerRegisterChannelEvent`, остальное — `dispatchIncomingMessage`. `Player.sendPluginMessage`/`Server.sendPluginMessage` шлют `custom_payload`, только если клиент объявил канал (как `CraftPlayer`); после `PlayerJoinEvent` клиенту уходит `minecraft:register` с входящими каналами. Проверено ботом `payload_probe` и плагином-эхо. Также `getServicesManager` (`SimpleServicesManager`, снятие при выключении плагина), `isStopping`, `getUnsafe` (только `getProtocolVersion` 777 и `getDataVersion` 5023) | | События входа, кика, телепорта и ввода | готово | Порядок Paper после авторизации: `AsyncPlayerPreLoginEvent` (и устаревший `PlayerPreLoginEvent` при слушателях) в своём потоке `User Authenticator #N` с `PlayerProfile` (свойства session server) и `PlayerLoginConnection`; затем проверки ванили `canPlayerLogin` (бан IP и игрока, белый список, места — без обхода для операторов) и `PlayerConnectionValidateLoginEvent` с их причиной, поверх — `PlayerLoginEvent` (как `HorriblePlayerLoginEventHack`); игрок события уходит в `PlayerJoinEvent`. `PlayerKickEvent` от `/kick` (`multiplayer.disconnect.kicked`), `/ban` (`BANNED`), `/ban-ip` (`IP_BANNED`) и `Player.kick`: отмена, своя причина, сообщение о выходе и `QuitReason.KICKED`; причина доходит клиенту пакетом `disconnect`. `PlayerRespawnEvent` (`DEATH`, `END_PORTAL`, кровать/якорь, смена точки), `PlayerTeleportEvent` от `/tp` (`COMMAND`) и жемчуга (`ENDER_PEARL`, отмена — без урона), `PlayerItemHeldEvent` (отмена возвращает слот), `PlayerToggleSneakEvent`, `PlayerToggleSprintEvent`. Также `getPlayerProfile`, `getAddress`, `Server.createProfile`. Тест `LoginEventsTest`; живой прогон тестовым плагином и `opcmd_probe` (`INPUTS=1`, `RESPAWN=1`) — все события пришли. Не сделано: `hostname` из handshake пуст, `InventoryOpenEvent` для ванильных контейнеров | | Клиенты других версий (ViaVersion) | частично | jar-ы ViaVersion, ViaBackwards, ViaRewind из `plugins/` поднимаются библиотекой (`ViaLoader`, своя платформа в `java/dist/via/chunkwake-via.jar`), а не Bukkit-плагином: их Bukkit-платформа внедряется в Netty Java-сервера. Клиент «чужой» версии получает сессию перевода (`Translator` в `chunkwake_net`): каждый пакет после распаковки идёт через `UserConnection` на `EmbeddedChannel` в JVM, клиенты 26.3 идут мимо. Статус отвечает каждой версии её протоколом (`via_probe`: 1.7–26.3). Клиенту без пака `minecraft:core:26.3` реестры уходят с данными записей из JSON датапака, как у ванилы. Бот 1.21.1 (`via_login_probe`) проходит вход, configuration и play (чанки, сущности, keep-alive). Клиенты до 1.8 входят без сжатия. В `PluginManager` стоят плагины Via с их версиями, `/viaversion` работает, классы Via видны плагинам (`Via.getAPI()`), `Player.getProtocolVersion` — версия клиента. Не проверено живыми клиентами старых версий; Via-кик закрывает соединение без пакета disconnect | | `World.save()` | готово | Просит сервер записать мир на диск; `/save-all` идёт тем же путём (`take_save_request` в автосейве). BlueMap зовёт это при включении | | Каталог мира плагинам | готово | `Server.getLevelDirectory()` и `getWorldContainer()` отдают настоящий каталог мира (абсолютный путь), `World.getWorldPath()` — каталог измерения, как `CraftWorld`: `dimensions//<путь>`. Плагины вправе читать оттуда `level.dat` и `region/` сами, как делает BlueMap (он берёт `getLevelDirectory` и `getKey`) | | Миры, Metadatable и PDC | частично | `getWorlds()` — оверворлд, Незер и Энд под именами Paper (`world`, `world_nether`, `world_the_end`) с ключами, окружением, высотами; `getWorld` по имени, UUID и ключу. UID мира из `data/paper/metadata.dat`, PDC мира в `data/paper/persistent_data_container.dat` (формат Paper, пишется при изменении). Блоки, мобы, `spawnEntity`, `getEntities`, `dropItem` — в своём измерении (запрос `@<ключ>` к Rust). `Player.getWorld`/`getLocation` по измерению игрока, `teleport` между мирами (`PlayerTeleportEvent` с мирами `from`/`to`, затем `PlayerChangedWorldEvent`; он же — при респавне в другом измерении). `Metadatable` у игроков, сущностей, миров и блоков на `MetadataStoreBase` (ключи как у CraftBukkit), очистка при `disablePlugin`. PDC игрока хранится в NBT игрока (`BukkitValues`) и переживает перезаход. Тест `WorldsTest`. Не сделано: PDC сущностей и чанков не сохраняется, точка спавна мира фиксирована, события тика мобов и блоков — только в оверворлде, загрузка и создание миров (`createWorld`) | | Свет блока | готово | `Block.getLightFromSky()`/`getLightFromBlocks()`/`getLightLevel()` через подзапрос `light x y z` (пара `sky block`); в Rust — `WorldView::block_light_at` рядом с `sky_light_at`. Без этого карта BlueMap выходит чёрной | | Снимок игрока | готово | `Player.isSneaking` из `Live::sneaking`, ставится прямо из `ServerboundPlayerInputPacket` (без ожидания тика, с инвалидацией кэша снимка). `getMetadata` — общее хранилище метаданных плагинов (см. «Миры, Metadatable и PDC») | | Проверено на живом сервере | готово | BlueMap 5.29 включается, грузит ресурсы Mojang (`accept-download: true`), поднимает веб-сервер и **рендерит карту**: `Loaded!`, `maps/world/tiles` — 66 тайлов на 1.1 МБ. Свет блока и присед игрока отдаются из мира (`light x y z` в `world_request`, `Live::sneaking`), поэтому карта не чёрная. Тестовый плагин включается рядом |