Актуальность сверки: 19 июля 2026 года.
Статус: по текущему дереву исходников WI-001–WI-007 реализованы и статически просмотрены; работоспособность не подтверждена, выпуск запрещён до прохождения WI-008.
Область: клиент Last Chaos, GameServer, server/client SQLite-каталоги и PostgreSQL lc_game.
[!WARNING] Этот документ описывает текущий исходный код, а не готовый production-релиз. Сборка, ECC-генерация EntitiesMP, тесты, запуск клиента/сервера и миграции не выполнялись. PostgreSQL-миграции
0024–0032и новые SQLite-схемы, согласно журналу прогресса, в рабочие базы не применялись; production-контент WI отсутствует. Настоящая рецензия также была только статической: SQLite/PostgreSQL не открывались и SQL из примеров не исполнялся.
Система объединяет:
Главное правило: клиент показывает состояние и отправляет намерение; решение и commit принадлежат серверу. Preview, локально активная кнопка, countdown или наличие visual не являются разрешением. Перед commit сервер повторно проверяет actor/target, area/layer, дистанцию, LoS, права, состояние, revision, inventory, NAS, labor и предметный контекст.
| Пункт | Использование | Текущее поведение | Сопровождение | Расширение |
|---|---|---|---|---|
| WI-001 | Очки работы | Operation/labor | WI-001 | Labor/actability |
| WI-002 | NPC actions | NPC context | WI-002 | NPC action |
| WI-003 | Объекты мира | Graph/objects | WI-003 | Graph/object |
| WI-004 | Timed crafting | Timed crafting | WI-004 | Recipe |
| WI-005 | Лут трупа | Corpse loot | WI-005 | Loot/policy |
| WI-006 | Housing | Housing | WI-006 | Housing/decor |
| WI-007 | Cultivation/resources | Cultivation/resources | WI-007 | Seed/crop/resource |
PlayerInfo.xml:pb_labor1 — account labor;pb_labor2 — character labor;txt_labor — сумма и раздельные значения.Player.es подходит к цели и запрашивает discovery у сервера.1–9; все 16 доступны кликом по иконке. Для позиций 10–16 цифровая подсказка намеренно не рисуется.Сессия закрывается при смене цели, смерти, warp/logout, смене area/layer, выходе из серверной дистанции или stale revision. Живой NPC обслуживается в пределах server range 8.0; клиент не пытается заменить эту проверку собственной геометрией.
defaultActionId, который ранее опубликовал сервер.Ок.Running UI показывает оставшиеся секунды; повторный start не отправляется.Отмена во время Running запрашивает cancel. Возвращаются только ещё не зафиксированные ресурсы.Claimable текст просит освободить inventory и снова нажать Ок: эта же кнопка выполняет claim.View loot или Take all.Поднять все — серверный take-all; список entry клиент не задаёт;Shift+клик — Greed, Ctrl+клик — Pass.Если policy отсутствует или публикация container не удалась, новая смерть остаётся на legacy ground-drop path. Для durable policy труп может быть восстановлен после restart только в устойчивом world/zone/area scope; reusable instance без стабильной identity fail closed возвращается к ground drop.
Ctrl+H — открыть/закрыть housing.xml. Hotkey обрабатывается только в gameplay, не в чате и не при фокусе edit box.Размещение:
Character, Account, Guild, Family) и Rotation.Preview и дождаться успешного ответа сервера.Place.Управление выбранным plot:
Contribute construction — contribution для server-published target stage/action;Set access entry — выбрать subject (Public, Account, Character, Guild, Family), ID и именованные права;Place decoration — template, anchor и local transform; сервер проверяет bounds/socket/overlap и exact source item;Remove selected decor;Remove plot — с подтверждением; active cultivation может блокировать удаление.Восемь текущих прав: View, Contribute, Decorate, ManageAccess, Remove, Plant, Care, Harvest. UI не принимает неизвестные биты. Для public subject ID должен быть 0, для остальных — положительный.
Ctrl+G — открыть/закрыть cultivation.xml.Посадка:
Capture current point: фиксируются текущие x/z/layer персонажа.Plant selected seed. Если персонаж отошёл или сменил layer, точку надо захватить заново.Уход и сбор:
Care, Harvest, Remove, Gather включаются только при наличии соответствующего server action ID;Remove требует подтверждения;Plant/Care/Harvest/Remove;Countdown строится из serverNowUnixMs и authoritative deadline. Достигнув нуля, клиент показывает ожидание, но не переводит стадию: нужен новый server upsert. Mature entity отображается как готовая к сбору.
Рабочий порядок всегда такой:
items, skills, t_npc, crafts.resource_kind=1.PRAGMA foreign_key_check) и предметные loader-инварианты из разделов 5 и 7.world_interaction_schema_versions.content_revision при server gameplay content change и world_visual_catalog_metadata.catalog_revision в каждой изменённой visual copy (client и обязательном cultivation mirror на server).CraftCatalog загружается позднее через mCraft.Init(). Client visual catalog загружается лениво один раз на процесс. Подтверждённой production-команды hot reload в текущем коде нет.0024–0032 в lc_game и согласованные SQLite-копии развернуты на сервер/клиент.enabled, но не удаляет durable rows.world click / UI button
-> client controller validates only local shape/pending state
-> CNetwork writer (existing transport)
-> GameServer bounded do_* packet handler
-> area/domain service re-resolves actor and target
-> static immutable SQLite snapshot + PostgreSQL repository transaction
-> authoritative result/upsert/snapshot
-> SessionStateExten bounded reader
-> client manager (state/visual) -> controller -> XML UI
Новые extend-группы: MSG_EX_WORLD_INTERACTION_LABOR, _NPC, _OBJECT, _HOUSING, _CULTIVATION; corpse V2 остаётся в MSG_EX_LOOT_SYSTEM, timed craft — в существующем MSG_CUSTOM_MESSAGE/MSG_CRAFT. Старые numeric values не переопределялись; новые значения добавлены в конец диапазонов.
world_interaction_operations даёт operation ID, per-character UUID request key, actor/target identity, state и optimistic revision.Created(0), Reserved(1), Running(2), Committed(3), Released(4), Failed(5).recovery_cursor_at, online/offline rates и max_offline_ticks. Клиентское время не участвует.CharacterLabor::SetModifiers(), не найден: не считать modifier rows активными без отдельной проверки caller.NpcContextAction.cpp сопоставляет legacy a_flag/a_flag1 с шестью ID 1–6 и icon IDs 1–6.world_interaction_npc_bindings существует в schema, но текущий InteractiveObjectCatalog::Load() его не читает. INSERT туда не добавляет живому NPC новую кнопку.CPC с target runtime/proto IDs и revision. Execute принимает только action из повторно построенного списка той же session.InteractiveObjectArea; async DB code использует IDs/value DTO, не CPC*.interactive_object_transitions. Delay и SkillEffect node загружаются, но generic evaluator fail closed при выполнении; persistent delay нужно моделировать state duration_ms + automatic_next_state_id.crafts, craft_materials, craft_products; generation — deterministic hash gameplay-полей.catalog_generation и output roll/snapshot до таймера.Claimable переживает restart и полный inventory.a_inventory_revision защищает от stale save, a_origin_key — exact-once delivery provenance.world_interaction_action_crafts target state обязан отсутствовать.Available(0) -> Reserved(1) -> Awarded(2); durable claim дополнительно защищён DB locks/unique constraints и inventory revision.world_interaction_actions target profile и graph нужны как schema-зависимости общей таблицы actions, но corpse runtime не выполняет generic graph и не берёт range из target profile. Discovery/execute идёт через NpcInteractionService с фиксированным текущим range 8.0, а Open/TakeAll маршрутизируются в CorpseLootArea.action_id, наличие соответствующего skill, fixed labor и server range 12.0; generic action graph/target profile для housing contribution не исполняется. Они остаются обязательными ссылками строки world_interaction_actions, а не заменой housing validation.cultivation_growth_jobs; generic object transitions для этих templates запрещены.resource_node_states.respawn_at.interactive_object_instances.revision.При включении area текущий порядок:
InteractiveObjectArea::Restore();CorpseLootArea::Restore();HousingArea::Restore();CultivationArea::Restore().Затем visibility отправляет object, housing и cultivation snapshots. Каждую секунду идут timed craft, object timers, corpse expiry/roll и cultivation growth/resource respawn ticks; раз в минуту освобождаются истёкшие labor reservations. При disable/level teardown area state очищается; PostgreSQL остаётся источником recovery.
| Слой | Основные файлы | Ответственность |
|---|---|---|
| Server startup | GameServer/Server.cpp, Craft.cpp |
Порядок загрузки catalog, инициализация craft |
| Server scheduling | ServerTimer.cpp, Area.cpp |
1-second ticks, minute recovery, area restore/snapshot |
| WI-001 Operations/labor | WorldInteractionOperation.*, LaborCatalog.*, LaborService.*, CharacterLabor.*, CharacterActability.* |
Idempotency, wallets, ledger, regeneration, actability |
| WI-002 NPC | NpcContextAction.*, NpcInteractionService.*, NpcInteractionPackets.*, doFuncNpcInteraction.cpp |
Discovery/session/execute |
| WI-003 Objects | InteractiveObjectCatalog.*, InteractiveObjectRepository.*, InteractiveObjectArea.*, packets/handler |
Graph, persistent states, timers, replication |
| WI-004 Craft | CraftCatalog.*, TimedCraftRepository.*, TimedCraftService.*, TimedCraftPackets.*, Craft.* |
Recipe generation, escrow, job, claim |
| WI-005 Corpse | CorpseLootCatalog.*, CorpseLootArea.*, CorpseLootRepository.*, packets/handler |
Publication, claims, rolls, expiry |
| WI-006 Housing | HousingCatalog.*, HousingGeometry.*, HousingRepository.*, HousingArea.*, packets/handler |
Geometry, placement, stages, rights, decor |
| WI-007 Cultivation | CultivationCatalog.*, CultivationRepository.*, CultivationArea.*, packets/handler |
Plant/growth/harvest/resource respawn |
| Shared opcodes | ShareLib/MessageType.h |
Server wire enums |
| Client network | Engine/Network/MessageDefine.h, CNetwork.cpp, SessionStateExten.cpp |
Writers, bounded readers, routing |
| Client state/controller | NpcInteractionController.*, WorldInteractionObjectManager.*, CorpseLootController.*, HousingManager/Controller.*, CultivationManager/Controller.*, Custom/Craft.* |
Local projection, pending fences, UI calls |
| Client UI | UIInteractActions.*, UILootWindow.*, UIHousing.*, UICultivation.*, UIPlayerInfo.* |
Presentation/input only |
| XML | interactActions.xml, lootWindow.xml, housing.xml, cultivation.xml, Craft.xml, PlayerInfo.xml |
Layout и реальные labels/buttons |
| World entry points | EntitiesMP/Player.es, Enemy.es, PlayerWeapons.es, ModelHolder2.es, ModelHolder3.es |
Click/ray target и visual overrides |
| Static schemas | WI System/world_interaction*_sqlite_schema.sql |
Authoring contracts |
| Durable schema | db/migrations/lc_game/0024..0032 |
PostgreSQL runtime state |
| Пункт | Что сохранять при изменениях | Обязательная соседняя сверка |
|---|---|---|
| WI-001 | Idempotency request key, character-first split, wallet/ledger transaction, absolute actability sync | Labor SQLite, PostgreSQL 0024/0025, packets и PlayerInfo.xml |
| WI-002 | Повторный server discovery, session revision, teardown и максимум 16 bounded actions | Legacy NPC flags/services, client icons/adapters и corpse routing |
| WI-003 | Immutable validated snapshot, area ownership, generation/revision и persistent timer recovery | Action/graph/object rows, PostgreSQL 0026, packet replication и visual binding |
| WI-004 | Pinned recipe generation, escrow, durable claim и inventory revision fence | Обе recipe copies, PostgreSQL 0027, NPC/workbench entry и craft UI |
| WI-005 | Publication-before-fallback, frozen eligibility, exact-once claim и partial take-all | Policy/action rows, PostgreSQL 0028, corpse V2 packets и legacy ground drop |
| WI-006 | Server geometry, occupancy transaction, named rights и exact decor source item | Housing SQLite, PostgreSQL 0029/0030, UI/click entry и stage/decor visuals |
| WI-007 | Единственный growth/respawn scheduler, object revision, claim lease и housing rights | Все три server schemas, PostgreSQL 0032, mirrored SKA visuals и UI/click entry |
Любое изменение packet/shared behavior сверять на client и server. Изменение runtime persistence сверять с соответствующей PostgreSQL migration и recovery path; изменение static identity — с server/client SQLite-копиями. Нельзя считать loader/schema row активной только потому, что она существует в DDL: отдельно учитывать перечисленные в разделах 3 и 5 неиспользуемые bindings и fail-closed semantics.
Фактический server order в Server.cpp: базовые item/character managers -> LaborCatalog -> InteractiveObjectCatalog -> legacy item proto list -> CorpseLootCatalog -> HousingCatalog -> CultivationCatalog; позднее mCraft.Init() загружает CraftCatalog. Такой порядок обязателен из-за cross-validation cultivation с object/loot/housing/labor и corpse с item protos.
Client DatabaseManager открывается из Engine.cpp; WI visual managers загружают tables лениво при первом snapshot/upsert. Их catalogLoadAttempted_ не сбрасывается при обычном scope reset, поэтому замена client DB внутри процесса не является поддерживаемым reload workflow.
Поддерживайте границы слоёв: packet handler только разбирает bounded payload и вызывает domain API; catalog не пишет runtime; repository не управляет UI/area pointers; area/service владеет gameplay orchestration; client manager хранит read-only projection; controller ставит pending fence; XML/UI только отображает и отправляет намерение.
*.es; сгенерированные .cpp/.h — только output ECC, их вручную не редактировать.Engine/Entities/Entity.h; реализация override находится в *.es.CEntity::InitializeWorldInteractionVisual() с overrides в ModelHolder2.es/ModelHolder3.es; CEntity::GetNpcTeleportFlags() с override в Enemy.es..es нужна разрешённая ECC regeneration; до неё generated Player.cpp может оставаться устаревшим.| Хранилище | Ownership | Что хранит | Что не хранит |
|---|---|---|---|
| Server compact.db (SQLite) | content team/server deployment | Gameplay definitions, recipes, policies, authored spawns, labor profiles; для cultivation также зеркальные visual IDs | Балансы, jobs, claims, plots, plants |
| Client compact.db (SQLite) | client content/package | Новые WI visual tables; кроме них в этой же legacy DB уже находятся recipes/categories/items и другие presentation data | Runtime-права, балансы, authoritative доступность action |
PostgreSQL lc_game |
GameServer | Durable operations, reservations, wallets, object instances, jobs, loot, housing, cultivation | Authoring resource paths и production content definitions |
visual_asset_id — общий числовой контракт, но server SQLite не должен получать путь от клиента. revision runtime-объекта не равен content_revision catalog. generation отличает incarnation/pinned catalog; он не заменяет revision.
| Таблица | Ключевые поля |
|---|---|
world_interaction_schema_versions |
component, version, content_revision; housing generation читает world_interaction revision |
world_interaction_target_profiles |
target_kind, max_range, LoS/dead flags; loader сейчас использует не все schema-поля |
world_interaction_requirement_sets / requirements |
match_mode, ordered requirement_kind, typed value columns, invert_result |
world_labor_profiles |
initial/max, tick, online/offline rates, offline cap, enabled |
world_labor_profile_tiers |
max/rate deltas или replacements, basis-point multipliers |
world_labor_modifier_rules |
source kind/id, stacking group/priority, deltas/multipliers |
world_labor_cost_profiles |
source, fixed/minimum cost, actability reward parameters |
world_actability_levels |
(actability_id, level), cumulative EXP threshold и multipliers |
world_interaction_graphs/nodes/edges/node_effects |
entry/failure, execution budget, typed nodes/edges/effects |
world_interaction_actions |
icon/text IDs, target/requirements/graph, optional skill/labor, cast/cooldown/flags, enabled |
world_interaction_object_templates/states/state_actions/spawns |
initial state, persistence/radius, visual/timer, action transition, stable authored placement |
world_interaction_action_crafts |
один action -> один crafts.id workbench binding |
Таблицы world_interaction_skill_actions и world_interaction_npc_bindings присутствуют в schema, но текущий object loader их не читает. Не считать их активными bindings.
| Домен | Таблицы |
|---|---|
| Legacy craft content | crafts, craft_materials, craft_products; сервер реально читает actability_id, actability_limit, delay, recommend_level, cost, labor_point, craft_npc, material/product grade/rate |
| Loot | world_interaction_loot_packs, world_interaction_loot_entries, world_interaction_corpse_policies, world_interaction_npc_corpse_policies, world_interaction_corpse_policy_actions |
| Housing | world_building_templates, world_building_stages, world_building_stage_materials, world_building_interiors, anchors/cultivation areas, territory/scopes/polygons/vertices/category rules, decoration templates |
| Cultivation | world_cultivation_profiles/stages/planting_bindings, world_housing_cultivation_rules, environment rules, world_resource_profiles/spawns |
| Поле | Значения текущего кода |
|---|---|
| target kind | 0 None, 1 Character, 2 Npc, 3 InteractiveObject, 4 Corpse, 5 Building, 6 Cultivation, 7 GatheringObject (runtime operation также имеет 8 HousingPlot, 9 HousingDecoration, но SQLite target schema допускает только 0–7) |
graph node_kind |
0 Entry, 1 Requirement, 2 Branch, 3 Delay, 4 SkillEffect, 5 SetObjectState, 6 Complete, 7 Fail; schema 8–19 пропускает CHECK, но loader их отклонит |
edge outcome_kind |
0 Any, 1 Success, 2 Failure; schema 3–5 loader отклонит |
| requirement | 0 CharacterLevel, 1 CharacterAlive, 2 ItemCount, 3 ObjectState, 4 ObjectLifecycle, 5 Zone, 6 Area, 7 Layer, 8 Distance, 9 CharacterFlag, 10 QuestState, 11 SkillKnown, 12 Permission, 13 WorldState |
requirement match_mode |
0 all, 1 any |
| labor modifier source | 0 AccountTier, 1 CharacterTag, 2 Buff, 3 Item, 4 WorldState |
| labor cost source | 0 None, 1 Fixed, 2 Action, 3 Recipe; cultivation/housing требуют positive Fixed |
loot claim_scope |
0 Owner, 1 Party, 2 Expedition, 3 Contributors |
| loot action role | 0 Open, 1 TakeAll |
loot roll_mode |
0 weighted draws with replacement (кроме уже использованного ненулевого unique_group), 1 without replacement, 2 without replacement + need/greed/pass |
| housing category | 0 House, 1 Farm, 2 Scarecrow |
| footprint/area shape | 1 Circle, 2 Box |
| anchor kind | 1 Floor (implicit anchor_id=0), 2 Wall, 3 Ceiling, 4 Socket; authored anchor rows допускают 2–4 |
| owner kind | 1 Character, 2 Account, 3 Guild, 4 Family |
| access subject | 0 Public, 1 Account, 2 Character, 3 Guild, 4 Family |
| permissions | bits 0..7: View, Contribute, Decorate, ManageAccess, Remove, Plant, Care, Harvest |
| cultivation access | 1 Housing, 2 PublicWorld |
Generic object evaluator поддерживает requirements 0,1,3,4,5,6,7,8; остальные fail closed. Cultivation дополнительно допускает SkillKnown(11) в собственном validator/evaluator path, но запрещает ItemCount/flag/quest/permission/world-state в action graph этой версии.
| Таблица | Контракт |
|---|---|
world_visual_catalog_metadata |
единственная row id=1, положительный catalog_revision; текущие managers его не сравнивают по wire |
world_visual_assets |
positive ID, kind/path, optional animations, scale/offset/rotation, flags/enabled |
world_building_stage_visuals |
exact (building_template_id, stage_id) -> visual_asset_id, одна complete row на template |
world_object_state_visuals |
exact (object_template_id, state_id) -> visual_asset_id |
world_housing_decoration_visuals |
exact decoration template -> visual asset |
Schema допускает resource kinds 1–7 (SkaModel, StaticModel, Billboard, Effect, TerrainDecoration, Particle, Composite), но текущая фактическая поддержка уже:
1, путь .smc;1 .smc или 2 .mdl;.., drive/URI и doubled separators.Синхронизация identity различается по доменам:
world_interaction_object_states.visual_asset_id должен совпасть с client (object_template_id,state_id)->visual_asset_id;visual_asset_id должен совпасть с client world_building_stage_visuals/world_housing_decoration_visuals; server loader не читает эти client binding tables;world_visual_assets и world_object_state_visuals обязаны присутствовать и в server compact.db, потому что CultivationCatalog читает их сам и принимает только resource_kind=1;catalog_revision сейчас не передаётся по wire и не доказывает согласованность: сверять надо сами IDs, kinds и paths.| Migration | Основные таблицы/изменения |
|---|---|
| 0024 | world_interaction_operations, inventory/currency reservations; unique character ownership constraint |
| 0025 | account/character labor, reservations, ledger |
| 0026 | interactive_object_instances, scheduled transitions |
| 0027 | craft jobs/material escrow/output/deliveries; t_characters.a_inventory_revision, t_inven.a_origin_key |
| 0028 | corpse containers/entries/participants/requests/claims/rolls/votes |
| 0029 | families, plots, occupancy, buildings |
| 0030 | construction, contributions, five permission tables, decorations |
| 0031 | operation events table, предназначенная для append-only audit |
| 0032 | cultivated entities, occupancy/usage, growth jobs, care actions, resource states, gathering claims |
world_interaction_operation_events создан миграцией как таблица, предназначенная для append-only истории, но DDL не запрещает UPDATE/DELETE, а writer в текущих GameServer WI units не найден. Не полагаться на неё как на полный или технически неизменяемый audit trail до отдельного подтверждения прав БД и writer-а.
Подтверждённые operation kinds для диагностики: craft 4, corpse 5, housing place/contribute/permission/decor/remove 6..10, plant/care/harvest/remove/gather 11..15.
Ключевые runtime contracts:
| Runtime row | Identity/revision/FK contract |
|---|---|
| operation | unique (actor_character_id, request_key), FK actor (character, account), target kind/id и state/revision |
| inventory/currency/labor reservation | FK на ту же operation/owner; active inventory slot уникален; labor split зафиксирован character-first |
| labor wallet/ledger | account и character rows раздельны; pool_kind=0 account, 1 character; persisted cursor/revision |
| interactive object | object_id, scope, phase, generation>0, revision; authored source tuple уникален; transition хранит expected revision/deadline |
| craft job/output | owner + operation, recipe и pinned catalog generation, job revision/state; output имеет unique delivery UUID/serial/origin |
| corpse | source identity и container generation/revision; participant IDs frozen; entry/claim uniqueness запрещает double award |
| housing | plot scope/owner/revision + unique occupancy cells; building связан с plot и interactive object; access principals разнесены по таблицам |
| cultivation/resource | cultivated entity связан с interactive object и optional plot; authoritative revision остаётся у object; growth/respawn/claim имеют отдельные deadlines/lease |
Подтверждённые runtime state values, полезные при read-only диагностике: craft 1 Running, 3 Claimable, 4 Delivered, 5 Cancelled, 6 Failed; corpse entry 0 Available, 1 Reserved, 2 Awarded; roll choice 0 None, 1 Pass, 2 Greed, 3 Need; resource state 0 Active, 1 Depleted; cultivation access 1 Housing, 2 PublicWorld. Для остальных таблиц не интерпретировать весь разрешённый CHECK-диапазон как реализованный enum.
[!CAUTION] Каждый schema file уже содержит
BEGIN IMMEDIATE; ... COMMIT;. Не оборачивать.read/execution этих файлов во внешнюю transaction. Применять каждый файл отдельно и только после byte-for-byte backup.
Скрипты используют CREATE TABLE IF NOT EXISTS: это bootstrap schema, а не универсальный ALTER-upgrader. Если таблица с тем же именем уже существует в другой форме, повторный запуск молча не исправит её definition. Перед применением сравнить sqlite_master staging-копии с source DDL; сами scripts не содержат production rows.
Server compact.db:
WI System/world_interaction_sqlite_schema.sql;WI System/world_interaction_client_sqlite_schema.sql;WI System/world_interaction_cultivation_sqlite_schema.sql.Шаг 2 на server DB не опечатка: CultivationCatalog в текущем коде читает world_visual_assets и world_object_state_visuals из того же server DatabaseManager, чтобы сверить exact visual ID и потребовать SKA kind.
Client compact.db:
WI System/world_interaction_client_sqlite_schema.sql.Main/cultivation gameplay schemas клиенту не нужны. Строки visual catalog, используемые cultivation, должны быть идентично подготовлены в server и client copies.
Server DB path строится простой конкатенацией startup AppPath и config Data SQLite/Path, key берётся из Data SQLite/Encrypt Key; автоматического path join/normalization нет, поэтому Path должен быть именно ожидаемым deployment suffix. Client сначала ищет physical Data\compact.db, затем может читать архивную immutable copy; physical/archive DB пробуется сначала как plain, затем с project SQLCipher key. Не редактировать archive-backed DB во время работы клиента и не раскрывать ключ из исходников/конфигурации.
Применять только к lc_game PostgreSQL в lexical order:
0024_world_interaction_operations.sql;0025_labor_pools.sql;0026_interactive_objects.sql;0027_timed_crafting.sql;0028_corpse_loot.sql;0029_housing_core.sql;0030_housing_construction_and_access.sql;0031_world_interaction_audit.sql;0032_cultivation_and_resources.sql.Не открывать эти files SQLite и не «адаптировать» PostgreSQL DDL вручную. Project migration stream использует schema_migrations; до применения проверить backup, текущую версию, согласованность t_characters, права DDL и свободное окно обслуживания. В этом документе команды миграции намеренно не выполнялись.
Файлы 0024–0032 не содержат собственных BEGIN/COMMIT. Project tool Server/Sources/tools/db_migrate.cpp читает каждый файл, исполняет его и вставляет соответствующую row в schema_migrations внутри одной pqxx::work transaction на файл. При ошибке текущий файл и его migration marker должны откатиться вместе; уже committed предыдущие migrations не откатываются. Внешний migration tool обязан обеспечить эквивалентную per-file atomicity и не помечать version применённой раньше успешного DDL. Обратных down-migrations эти файлы не предоставляют.
sqlite3 может принять encrypted file за повреждённый или создать неверную копию.BEGIN IMMEDIATE ... COMMIT/ROLLBACK transaction.PRAGMA foreign_key_check, сверку server/client IDs и наличие ресурсов в package roots.Существующее действие 1–6:
NPC_SHOPPER, NPC_KEEPER, NPC1_TRADEAGENT, NPC_REFINER, NPC_ZONEMOVER, NPC1_FACTORY|NPC1_CRAFTING). Не копировать числовой bit из другого branch.Новый тип действия: одного INSERT в world_interaction_npc_bindings недостаточно. Нужны согласованные изменения NpcContextActionId client/server, server Build/validation, packet compatibility, client adapter/UI, teardown и icon. Для content-defined действия предпочтительнее WI-003 object action, если это не legacy NPC service.
target_kind=3).SetObjectState, либо next_state_id, а при обоих значения обязаны совпасть.world_interaction_action_crafts.crafts/craft_materials/craft_products rows в server и client content copies.delay — миллисекунды, максимум loader 23h; rate — 0..100; grade не ниже -1; cost/labor неотрицательны.actability_limit>0, actability_id должен быть положительным и иметь ожидаемый character actability content.craft_npc должен совпасть с подтверждённым Craft NPC.action_id -> craft_id.persist_across_restart=1.t_npc.a_index через world_interaction_npc_corpse_policies.persist=0), затем отдельно проверять durable/restart.world_interaction_target_profiles.max_range, LoS и generic graph у corpse action не управляют текущим corpse runtime: фактический discovery range равен 8.0, после чего CorpseLootArea повторно проверяет собственные distance/area/layer/generation/revision/eligibility. Не пытаться изменить corpse range только правкой target profile.
skill_id, positive fixed labor profile и материалы; required_actions <= material line count.is_complete identity.Для construction action target profile/graph должны существовать из-за FK общей action schema, но текущий housing handler не исполняет generic graph. Gameplay задают stage binding, skill, fixed labor/materials, housing rights/revisions и фиксированный server placement/interaction range. Не помещать housing reward/state mutation в generic graph в расчёте на его выполнение.
target_kind=6, synchronous, без cast/cooldown/flags, с positive fixed labor profile.target_kind=7 gather action, loot pack, respawn min/max и минимум один authored spawn.world_labor_profiles.id; самый маленький enabled ID становится default profile.actability_id и basis-point rate; levels задаются cumulative required_total_exp.requirement_kind: craft requirement использует crafts.actability_id/actability_limit; reward — labor cost profile.Это условия будущего content/release review, а не утверждение о выполненных проверках текущего кандидата:
enabled=0 использовать для подготовленного, но ещё не выпускаемого content.| Обязательное покрытие | Пример |
|---|---|
| Generic world object + action graph + visual binding | 8.2 |
| Living NPC action без выдуманного raw flag | 8.8 |
| Recipe caveat + workbench action mapping | 8.3 |
| Corpse loot policy | 8.4 |
| Housing territory/template/stages + decoration | 8.5 |
| Cultivation/seed/growth + authored resource/respawn | 8.6 |
| Отдельная сверка visual identity | 8.7 |
Все SQL blocks в этом разделе предназначены только для SQLite compact.db. Они не являются PostgreSQL migrations и не должны исполняться в lc_game; PostgreSQL lifecycle отдельно описан в 5.4 и 6.2.
Все жёстко заданные ID ниже — TEST ONLY из диапазона 910000–969999 и обозначают только rows, создаваемые внутри того же примера. Они не являются production IDs; перед использованием диапазон всё равно надо зарезервировать и проверить на коллизии. Любая внешняя logical reference, которую пример сам не создаёт (item, skill, NPC, icon, legacy craft category, world/zone/area/layer или localization text), записана как именованный bind-параметр :.... Подставлять догадку или ID из другой revision нельзя: параметр должен разрешаться в конкретной целевой copy, даже когда PRAGMA foreign_key_check не способен это проверить.
Каждый mutating snippet рассчитан только на отдельную disposable/staging-копию уже установленной schema и заканчивается ROLLBACK. Не заменять ROLLBACK на COMMIT в приведённом тексте: где DDL предоставляет enabled, примеры намеренно вставляют enabled=0; однако TEST paths, непроверенные bind values и таблицы без такого переключателя всё равно способны остановить server startup или сломать catalog. Для сохраняемого теста сначала подготовить отдельный reviewed вариант с доказанными logical refs/resources, выбранным TEST range, явным enable-step и cleanup plan, и только затем осознанно использовать COMMIT в отдельном файле.
Schema files из раздела 6 нельзя помещать внутрь этих snippets: у каждого schema script уже есть собственные BEGIN IMMEDIATE/COMMIT, поэтому schema install будет отдельным committed шагом и финальный ROLLBACK примера его не отменит. PRAGMA foreign_keys=ON задаётся до BEGIN; в SQLite попытка изменить этот режим внутри transaction не имеет нужного эффекта. Пустой результат PRAGMA foreign_key_check означает только отсутствие нарушений объявленных FK — это не C++ loader test, не проверка logical refs и не runtime test.
Порядок INSERT в примерах намеренный. FK graph -> entry/failure node и object template -> initial state объявлены DEFERRABLE INITIALLY DEFERRED, поэтому graph/template можно вставить перед node/state, но все parents обязаны появиться до commit/проверки. Self-FK stage -> next stage не deferrable; приведённые stage chains вставляются одним multi-row statement, где весь target set существует к завершению statement. Если разбивать такой INSERT, вставлять terminal stage раньше referencing stage либо проектировать отдельную корректную последовательность — не отключать FK.
Target DB: server compact.db.
Prerequisites: установлена main WI schema; выбран свободный range.
Verification SELECT: три SELECT ниже проверяют profile, cost и ordered actability levels; затем выполняется FK check.
Rollback/cleanup: snippet всегда завершает transaction через ROLLBACK; для отдельно reviewed committed staging rows порядок cleanup указан после блока.
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_labor_profiles (
id, code, account_initial, account_max, character_initial, character_max,
tick_seconds, account_online_per_tick, account_offline_per_tick,
character_online_per_tick, character_offline_per_tick, max_offline_ticks,
spend_policy, allow_combined_balance, overflow_policy, enabled
) VALUES (
910001, 'TEST_LABOR', 100, 100, 20, 20,
60, 1, 1, 1, 1, 1440,
0, 1, 0, 0
);
INSERT INTO world_labor_cost_profiles (
id, code, cost_source, fixed_cost, pool_policy, minimum_cost,
actability_id, actability_exp_per_labor_bps,
vocation_points_per_labor_bps, grant_character_exp
) VALUES (910010, 'TEST_FIXED_5', 1, 5, 0, 0, 910100, 10000, 0, 0);
INSERT INTO world_actability_levels (
actability_id, level, required_total_exp,
labor_cost_multiplier_bps, action_time_multiplier_bps, reward_multiplier_bps
) VALUES
(910100, 0, 0, 10000, 10000, 10000),
(910100, 1, 100, 9500, 9500, 10500);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
-- Verification SELECT:
SELECT id, code, account_max, character_max, enabled
FROM world_labor_profiles WHERE id = 910001;
SELECT * FROM world_labor_cost_profiles WHERE id = 910010;
SELECT * FROM world_actability_levels WHERE actability_id = 910100 ORDER BY level;
PRAGMA foreign_key_check;
ROLLBACK;
Committed cleanup order: actability levels -> cost profile -> profile. Не удалять profile, пока durable wallets с этим ID существуют.
Target DB: server compact.db для gameplay rows; client compact.db для visual rows. Visual rows также зеркалируются в server DB, если этот template будет использовать cultivation validation.
Prerequisites: main + visual schema; реальный packaged .smc вместо TEST path; существующие scope/text/icon bind values и проверенные coordinates.
Verification SELECT: server SELECT проверяют graph nodes, state/action transition и spawn; visual SELECT проверяет exact state/asset binding.
Rollback/cleanup: оба независимых блока заканчиваются ROLLBACK; cleanup order для отдельно committed staging set приведён после них.
-- SERVER compact.db
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_interaction_target_profiles
(id, target_kind, max_range, require_line_of_sight)
VALUES (920001, 3, 4.0, 1);
INSERT INTO world_interaction_graphs
(id, code, entry_node_id, max_transitions, timeout_ms, enabled)
VALUES (920010, 'TEST_TOGGLE_GRAPH', 920011, 8, 30000, 0);
INSERT INTO world_interaction_graph_nodes (id, graph_id, node_kind)
VALUES (920011, 920010, 0), (920012, 920010, 6);
INSERT INTO world_interaction_graph_edges
(graph_id, from_node_id, edge_order, to_node_id, outcome_kind, delay_ms, weight)
VALUES (920010, 920011, 0, 920012, 0, 0, 1);
INSERT INTO world_interaction_actions (
id, code, icon_id, name_text_id, description_text_id,
target_profile_id, graph_id, cast_time_ms, cooldown_ms, server_flags, enabled
) VALUES (
920020, 'TEST_TOGGLE', :toggle_icon_id,
:toggle_name_text_id, :toggle_description_text_id,
920001, 920010, 0, 0, 0, 0
);
INSERT INTO world_interaction_object_templates
(id, code, initial_state_id, persistent, interaction_radius, enabled)
VALUES (920100, 'TEST_SWITCH', 920101, 1, 4.0, 0);
INSERT INTO world_interaction_object_states
(id, object_template_id, state_order, visual_asset_id)
VALUES
(920101, 920100, 0, 920501),
(920102, 920100, 1, 920502);
INSERT INTO world_interaction_object_state_actions
(object_template_id, object_state_id, action_id, binding_order, next_state_id, exclusive_use)
VALUES (920100, 920101, 920020, 0, 920102, 1);
INSERT INTO world_interaction_object_spawns (
id, object_template_id, world_id, zone_id, area_id, layer_id,
position_x, position_y, position_z, rotation, enabled
) VALUES (
920200, 920100, :world_id, :zone_id, :area_id, :layer_id,
:position_x, :position_y, :position_z, :rotation, 0
);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
-- Verification SELECT:
SELECT g.id, n.id AS node_id, n.node_kind
FROM world_interaction_graphs g
JOIN world_interaction_graph_nodes n ON n.graph_id = g.id
WHERE g.id = 920010 ORDER BY n.id;
SELECT osa.object_template_id, osa.object_state_id, osa.action_id, osa.next_state_id
FROM world_interaction_object_state_actions osa
WHERE osa.object_template_id = 920100;
SELECT * FROM world_interaction_object_spawns WHERE id = 920200;
PRAGMA foreign_key_check;
ROLLBACK;
-- CLIENT compact.db; применить тот же visual subset и к server compact.db,
-- если он нужен CultivationCatalog для strict visual validation.
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_visual_assets
(id, resource_kind, resource_path, scale, enabled)
VALUES
(920501, 1, 'Data/WorldInteraction/TEST_switch_off.smc', 1.0, 0),
(920502, 1, 'Data/WorldInteraction/TEST_switch_on.smc', 1.0, 0);
INSERT INTO world_object_state_visuals
(object_template_id, state_id, visual_asset_id)
VALUES
(920100, 920101, 920501),
(920100, 920102, 920502);
UPDATE world_visual_catalog_metadata
SET catalog_revision = catalog_revision + 1 WHERE id = 1;
-- Verification SELECT:
SELECT osv.*, a.resource_kind, a.resource_path
FROM world_object_state_visuals osv
JOIN world_visual_assets a ON a.id = osv.visual_asset_id
WHERE osv.object_template_id = 920100;
PRAGMA foreign_key_check;
ROLLBACK;
Cleanup: spawn -> state action -> states -> template -> action -> edges -> nodes -> graph -> target; visual binding -> assets в каждой затронутой copy. Не удалять static identity, пока она нужна durable interactive_object_instances/transition recovery.
Target DB: одинаковые recipe/material/product rows в server и client compact.db; optional workbench mapping только в server DB.
Prerequisites: реальные item IDs, existing craft category visible клиенту, Craft NPC или no-transition workbench action.
Verification SELECT: preflight проверяет packaged DDL, recipe SELECT — созданные связи, mapping SELECT — точное сопоставление action/recipe.
Rollback/cleanup: DDL preflight read-only и не требует rollback/cleanup; оба mutating templates заканчиваются ROLLBACK, cleanup order указан ниже.
Три новые WI schema не создают legacy crafts/craft_materials/craft_products, а их canonical SQLite DDL отсутствует в source SQL этого дерева. Текущие server/client readers подтверждают используемые ниже column names, но не доказывают отсутствие дополнительных NOT NULL columns в конкретной packaged DB. Поэтому сначала отдельно, read-only проверить обе целевые copies; если есть дополнительная колонка без default, расширить reviewed INSERT, а не рассчитывать на этот шаблон. Следующий mutating block — именно условный template после успешного preflight, а не доказанно переносимый INSERT для любой packaged DB:
-- Read-only DDL preflight; no transaction, rollback or cleanup is applicable.
SELECT name, sql
FROM sqlite_master
WHERE type = 'table'
AND name IN ('crafts', 'craft_materials', 'craft_products')
ORDER BY name;
PRAGMA table_info('crafts');
PRAGMA table_info('craft_materials');
PRAGMA table_info('craft_products');
PRAGMA foreign_key_list('crafts');
PRAGMA foreign_key_list('craft_materials');
PRAGMA foreign_key_list('craft_products');
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
-- Bind values must be verified in both target copies before this template is prepared.
INSERT INTO crafts (
id, actability_id, actability_limit, delay, recommend_level, cost,
craft_c_category_id, labor_point, title, craft_npc
) VALUES (
930001, -1, 0, 5000, 1, 100,
:craft_category_id, 5, 'TEST timed recipe', :craft_npc_id
);
INSERT INTO craft_materials (id, amount, craft_id, item_id, require_grade)
VALUES (930011, 2, 930001, :material_item_id, -1);
INSERT INTO craft_products (id, amount, craft_id, item_grade_id, item_id, rate, use_grade)
VALUES (930021, 1, 930001, -1, :product_item_id, 100, 0);
-- Verification SELECT:
SELECT c.id, c.delay, c.cost, c.labor_point, c.craft_npc,
m.item_id AS material, p.item_id AS product, p.rate
FROM crafts c
JOIN craft_materials m ON m.craft_id = c.id
JOIN craft_products p ON p.craft_id = c.id
WHERE c.id = 930001;
PRAGMA foreign_key_check;
ROLLBACK;
Optional server workbench mapping после создания action без next_state_id/SetObjectState. craft_id — logical reference без SQLite FK: recipe 930001 и bind-параметр :workbench_action_id должны реально существовать в той же server DB. Поскольку recipe snippet выше заканчивается ROLLBACK, для общего disposable test mapping надо вставить до того же ROLLBACK, а не запускать следующий block как будто recipe сохранился:
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_interaction_action_crafts (action_id, craft_id)
VALUES (:workbench_action_id, 930001);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
-- Verification SELECT:
SELECT *
FROM world_interaction_action_crafts
WHERE action_id = :workbench_action_id;
PRAGMA foreign_key_check;
ROLLBACK;
TEST toggle action из 8.2 использовать нельзя: у него есть transition, поэтому runtime workbench его отвергнет. Cleanup recipe: action-craft mapping -> products -> materials -> crafts; active/pinned durable craft jobs сначала должны быть штатно завершены или обработаны утверждённым recovery/rollback plan, а не удалены вручную.
Target DB: server compact.db.
Prerequisites: main schema; item/NPC/text/icon bind values существуют в соответствующих server/client copies.
Verification SELECT: policy JOIN проверяет NPC binding и обе action roles, loot JOIN — pack entry; затем выполняется FK check.
Rollback/cleanup: блок заканчивается ROLLBACK; отдельно committed cleanup выполняется в порядке после блока и только с учётом durable containers.
Target profile и minimal graph ниже удовлетворяют FK/общему action loader contract. Они не меняют фактический corpse range и не исполняются как corpse reward graph; эти semantics принадлежат NpcInteractionService/CorpseLootArea.
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_interaction_target_profiles
(id, target_kind, max_range, require_line_of_sight, allow_dead)
VALUES (940001, 4, 8.0, 0, 1);
INSERT INTO world_interaction_graphs
(id, code, entry_node_id, max_transitions, timeout_ms, enabled)
VALUES (940010, 'TEST_CORPSE_UI_GRAPH', 940011, 4, 30000, 0);
INSERT INTO world_interaction_graph_nodes (id, graph_id, node_kind)
VALUES (940011, 940010, 0), (940012, 940010, 6);
INSERT INTO world_interaction_graph_edges
(graph_id, from_node_id, edge_order, to_node_id, outcome_kind)
VALUES (940010, 940011, 0, 940012, 0);
INSERT INTO world_interaction_actions (
id, code, icon_id, name_text_id, description_text_id,
target_profile_id, graph_id, enabled
) VALUES
(940020, 'TEST_CORPSE_OPEN', :corpse_open_icon_id, :corpse_open_name_text_id,
:corpse_open_description_text_id, 940001, 940010, 0),
(940021, 'TEST_CORPSE_ALL', :corpse_all_icon_id, :corpse_all_name_text_id,
:corpse_all_description_text_id, 940001, 940010, 0);
INSERT INTO world_interaction_loot_packs
(id, code, roll_mode, roll_count_min, roll_count_max, allow_empty)
VALUES (940100, 'TEST_CORPSE_PACK', 0, 1, 1, 0);
INSERT INTO world_interaction_loot_entries
(loot_pack_id, entry_order, item_id, weight, min_count, max_count, min_grade, max_grade)
VALUES (940100, 0, :loot_item_id, 100, 1, 1, 0, 0);
INSERT INTO world_interaction_corpse_policies (
id, code, lifetime_seconds, exclusive_claim_seconds, claim_scope,
loot_pack_id, destroy_when_empty, persist_across_restart
) VALUES (940200, 'TEST_MEMORY_CORPSE', 120, 15, 0, 940100, 1, 0);
INSERT INTO world_interaction_corpse_policy_actions
(corpse_policy_id, action_role, action_id)
VALUES (940200, 0, 940020), (940200, 1, 940021);
INSERT INTO world_interaction_npc_corpse_policies (npc_id, corpse_policy_id)
VALUES (:npc_proto_id, 940200);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
-- Verification SELECT:
SELECT p.id, p.persist_across_restart, b.npc_id, a.action_role, a.action_id
FROM world_interaction_corpse_policies p
JOIN world_interaction_npc_corpse_policies b ON b.corpse_policy_id = p.id
JOIN world_interaction_corpse_policy_actions a ON a.corpse_policy_id = p.id
WHERE p.id = 940200 ORDER BY a.action_role;
SELECT lp.id, le.entry_order, le.item_id, le.weight
FROM world_interaction_loot_packs lp
JOIN world_interaction_loot_entries le ON le.loot_pack_id = lp.id
WHERE lp.id = 940100;
PRAGMA foreign_key_check;
ROLLBACK;
Cleanup: NPC binding -> policy actions -> policy -> loot entries -> pack -> actions -> graph edges -> graph nodes -> graph -> target profile. Для committed durable policy не удалять definition, пока active PostgreSQL containers с этим policy ID нужны recovery.
Target DB: server compact.db + matching client visual rows.
Prerequisites: main/visual schema; все item/skill/text/icon/scope bind-параметры разрешаются в целевых copies; valid real coordinates/resources.
Verification SELECT: server SELECT проверяют ordered stages, polygon vertices и decor template; client SELECT — stage/decor visual bindings; оба блока завершаются FK check.
Rollback/cleanup: server и client blocks независимо заканчиваются ROLLBACK; cleanup для отдельно committed staging rows идёт от bindings/children к parents с проверкой durable references.
-- SERVER compact.db
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_labor_cost_profiles
(id, code, cost_source, fixed_cost, pool_policy, minimum_cost)
VALUES (950001, 'TEST_BUILD_5', 1, 5, 0, 0);
INSERT INTO world_interaction_target_profiles (id, target_kind, max_range)
VALUES (950010, 5, 12.0);
INSERT INTO world_interaction_graphs
(id, code, entry_node_id, max_transitions, timeout_ms, enabled)
VALUES (950020, 'TEST_BUILD_GRAPH', 950021, 4, 30000, 0);
INSERT INTO world_interaction_graph_nodes (id, graph_id, node_kind)
VALUES (950021, 950020, 0), (950022, 950020, 6);
INSERT INTO world_interaction_graph_edges
(graph_id, from_node_id, edge_order, to_node_id, outcome_kind)
VALUES (950020, 950021, 0, 950022, 0);
INSERT INTO world_interaction_actions (
id, code, icon_id, name_text_id, description_text_id, target_profile_id,
graph_id, skill_id, labor_cost_profile_id, enabled
) VALUES (
950030, 'TEST_BUILD_ACTION', :construction_icon_id,
:construction_name_text_id, :construction_description_text_id,
950010, 950020, :construction_skill_id, 950001, 0
);
INSERT INTO world_build_territories (id, code, priority, permission_mask, enabled)
VALUES (950100, 'TEST_TERRITORY', 0, 1, 0);
INSERT INTO world_build_territory_scopes (
id, territory_id, world_id, zone_id, area_id, y_layer, scope_order,
minimum_height, maximum_height, maximum_slope
) VALUES (
950110, 950100, :world_id, :zone_id, :area_id, :y_layer,
0, -100.0, 100.0, 4.0
);
INSERT INTO world_build_territory_polygons
(id, territory_id, territory_scope_id, polygon_order, is_exclusion)
VALUES (950120, 950100, 950110, 0, 0);
INSERT INTO world_build_territory_vertices (polygon_id, vertex_index, x, z)
VALUES
(950120, 0, 90.0, 90.0), (950120, 1, 110.0, 90.0),
(950120, 2, 110.0, 110.0), (950120, 3, 90.0, 110.0);
INSERT INTO world_build_category_rules
(territory_id, category_id, max_total, max_per_owner, min_spacing, permission_mask)
VALUES (950100, 0, 10, 1, 1.0, 1);
INSERT INTO world_building_templates (
id, code, category_id, blueprint_item_id, blueprint_item_count,
placement_nas_cost, initial_stage_id, max_health,
footprint_kind, footprint_radius, owner_limit, decoration_limit, enabled
) VALUES (950200, 'TEST_HOUSE', 0, :blueprint_item_id, 1, 0, 950201, 1000,
1, 2.0, 1, 4, 0);
INSERT INTO world_building_stages (
id, building_template_id, stage_order, action_id, build_time_seconds,
required_actions, next_stage_id, visual_asset_id, is_complete
) VALUES
(950201, 950200, 0, NULL, 0, 1, 950202, 950501, 0),
(950202, 950200, 1, 950030, 0, 1, NULL, 950502, 1);
INSERT INTO world_building_stage_materials
(stage_id, item_id, required_count, min_grade, material_order)
VALUES (950202, :construction_material_item_id, 1, 0, 0);
INSERT INTO world_building_interiors
(building_template_id, min_x, max_x, min_z, max_z, min_h, max_h, complete_only)
VALUES (950200, -1.5, 1.5, -1.5, 1.5, 0.0, 3.0, 1);
INSERT INTO world_housing_decoration_templates (
id, code, source_item_id, visual_asset_id, footprint_kind,
footprint_radius, footprint_half_x, footprint_half_z, footprint_height,
min_scale, max_scale, allowed_anchor_mask, socket_type, enabled
) VALUES (950300, 'TEST_FLOOR_DECOR', :decoration_item_id, 950503, 1,
0.25, 0.0, 0.0, 1.0, 1.0, 1.0, 1, 0, 0);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
-- Verification SELECT:
SELECT * FROM world_building_stages WHERE building_template_id = 950200 ORDER BY stage_order;
SELECT polygon_id, vertex_index, x, z FROM world_build_territory_vertices WHERE polygon_id = 950120;
SELECT id, source_item_id, visual_asset_id
FROM world_housing_decoration_templates
WHERE id = 950300;
PRAGMA foreign_key_check;
ROLLBACK;
-- CLIENT compact.db
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_visual_assets (id, resource_kind, resource_path, scale, enabled)
VALUES
(950501, 1, 'Data/WorldInteraction/TEST_house_foundation.smc', 1.0, 0),
(950502, 2, 'Data/WorldInteraction/TEST_house_complete.mdl', 1.0, 0),
(950503, 1, 'Data/WorldInteraction/TEST_floor_decor.smc', 1.0, 0);
INSERT INTO world_building_stage_visuals
(building_template_id, stage_id, visual_asset_id, is_complete)
VALUES (950200, 950201, 950501, 0), (950200, 950202, 950502, 1);
INSERT INTO world_housing_decoration_visuals (decoration_template_id, visual_asset_id)
VALUES (950300, 950503);
UPDATE world_visual_catalog_metadata
SET catalog_revision = catalog_revision + 1 WHERE id = 1;
-- Verification SELECT:
SELECT * FROM world_building_stage_visuals WHERE building_template_id = 950200;
SELECT *
FROM world_housing_decoration_visuals
WHERE decoration_template_id = 950300;
PRAGMA foreign_key_check;
ROLLBACK;
Cleanup выполняется от visual/bindings и territory vertices к родителям. Не удалять template, используемый PostgreSQL housing_buildings/housing_decorations recovery.
Target DB: server compact.db; visual subset — server и client compact.db.
Prerequisites: все три server schemas, visual schema client, все item/text/icon/scope bind-параметры существуют, resource paths реально packaged; PostgreSQL 0024–0032 применяются только до отдельно разрешённого runtime enable.
Verification SELECT: server SELECT проверяют ordered crop stages, seed binding и authored resource spawn; client SELECT — exact mirrored visuals; оба блока выполняют FK check.
Rollback/cleanup: server и client blocks независимо заканчиваются ROLLBACK; cleanup отдельно committed staging set приведён после блоков и не удаляет durable PostgreSQL rows.
-- SERVER compact.db: минимальный TEST crop + resource
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_labor_cost_profiles
(id, code, cost_source, fixed_cost, pool_policy, minimum_cost)
VALUES (960001, 'TEST_GATHER_1', 1, 1, 0, 0);
INSERT INTO world_interaction_target_profiles (id, target_kind, max_range)
VALUES (960010, 6, 12.0), (960011, 7, 12.0);
INSERT INTO world_interaction_graphs
(id, code, entry_node_id, max_transitions, timeout_ms, enabled)
VALUES (960020, 'TEST_SYNC_GRAPH', 960021, 4, 30000, 0);
INSERT INTO world_interaction_graph_nodes (id, graph_id, node_kind)
VALUES (960021, 960020, 0), (960022, 960020, 6);
INSERT INTO world_interaction_graph_edges
(graph_id, from_node_id, edge_order, to_node_id, outcome_kind)
VALUES (960020, 960021, 0, 960022, 0);
INSERT INTO world_interaction_actions (
id, code, icon_id, name_text_id, description_text_id,
target_profile_id, graph_id, labor_cost_profile_id,
cast_time_ms, cooldown_ms, server_flags, enabled
) VALUES
(960030, 'TEST_PLANT', :plant_icon_id, :plant_name_text_id, :plant_description_text_id,
960010, 960020, 960001, 0, 0, 0, 0),
(960031, 'TEST_CARE', :care_icon_id, :care_name_text_id, :care_description_text_id,
960010, 960020, 960001, 0, 0, 0, 0),
(960032, 'TEST_HARVEST', :harvest_icon_id, :harvest_name_text_id, :harvest_description_text_id,
960010, 960020, 960001, 0, 0, 0, 0),
(960033, 'TEST_REMOVE', :remove_icon_id, :remove_name_text_id, :remove_description_text_id,
960010, 960020, 960001, 0, 0, 0, 0),
(960034, 'TEST_GATHER', :gather_icon_id, :gather_name_text_id, :gather_description_text_id,
960011, 960020, 960001, 0, 0, 0, 0);
INSERT INTO world_interaction_loot_packs
(id, code, roll_mode, roll_count_min, roll_count_max, allow_empty)
VALUES (960100, 'TEST_HARVEST_PACK', 0, 1, 1, 0);
INSERT INTO world_interaction_loot_entries
(loot_pack_id, entry_order, item_id, weight, min_count, max_count, min_grade, max_grade)
VALUES (960100, 0, :harvest_item_id, 100, 1, 1, 0, 0);
INSERT INTO world_interaction_object_templates
(id, code, initial_state_id, persistent, interaction_radius, enabled)
VALUES
(960200, 'TEST_CROP_OBJECT', 960201, 1, 4.0, 0),
(960300, 'TEST_RESOURCE_OBJECT', 960301, 1, 4.0, 0);
INSERT INTO world_interaction_object_states
(id, object_template_id, state_order, visual_asset_id)
VALUES
(960201, 960200, 0, 960501), (960202, 960200, 1, 960502),
(960301, 960300, 0, 960503), (960302, 960300, 1, 960504);
-- Server-side mirror used by CultivationCatalog strict visual validation.
INSERT INTO world_visual_assets (id, resource_kind, resource_path, scale, enabled)
VALUES
(960501, 1, 'Data/WorldInteraction/TEST_crop_seed.smc', 1.0, 0),
(960502, 1, 'Data/WorldInteraction/TEST_crop_mature.smc', 1.0, 0),
(960503, 1, 'Data/WorldInteraction/TEST_ore_active.smc', 1.0, 0),
(960504, 1, 'Data/WorldInteraction/TEST_ore_depleted.smc', 1.0, 0);
INSERT INTO world_object_state_visuals (object_template_id, state_id, visual_asset_id)
VALUES
(960200, 960201, 960501), (960200, 960202, 960502),
(960300, 960301, 960503), (960300, 960302, 960504);
INSERT INTO world_cultivation_profiles (
id, code, object_template_id, category_id, placement_action_id,
care_action_id, harvest_action_id, remove_action_id, loot_pack_id,
allow_world_placement, allow_housing_placement, show_owner,
placement_radius, maximum_slope, placement_flags, enabled
) VALUES (960400, 'TEST_CROP', 960200, 0, 960030,
960031, 960032, 960033, 960100,
1, 0, 1, 0.5, 4.0, 0, 0);
INSERT INTO world_cultivation_stages (
id, cultivation_profile_id, object_template_id, stage_order,
object_state_id, duration_seconds, required_water, required_care,
next_stage_id, is_mature
) VALUES
(960401, 960400, 960200, 0, 960201, 60, 0, 0, 960402, 0),
(960402, 960400, 960200, 1, 960202, 0, 0, 0, NULL, 1);
INSERT INTO world_cultivation_planting_bindings
(seed_item_id, cultivation_profile_id, consume_count)
VALUES (:seed_item_id, 960400, 1);
INSERT INTO world_cultivation_environment_rules (
cultivation_profile_id, world_id, zone_id, area_id, layer_id,
growth_time_multiplier_bps, placement_allowed
) VALUES (
960400, :world_id, :zone_id, :area_id, :layer_id, 10000, 1
);
INSERT INTO world_resource_profiles (
id, code, object_template_id, gather_action_id, loot_pack_id,
depleted_state_id, respawn_seconds_min, respawn_seconds_max, enabled
) VALUES (960410, 'TEST_ORE', 960300, 960034, 960100, 960302, 60, 120, 0);
INSERT INTO world_resource_spawns (
id, resource_profile_id, world_id, zone_id, area_id, layer_id,
position_x, position_y, position_z, rotation, enabled
) VALUES (
960420, 960410, :world_id, :zone_id, :area_id, :layer_id,
:position_x, :position_y, :position_z, :rotation, 0
);
UPDATE world_interaction_schema_versions
SET content_revision = content_revision + 1
WHERE component = 'world_interaction';
UPDATE world_visual_catalog_metadata
SET catalog_revision = catalog_revision + 1
WHERE id = 1;
-- Verification SELECT:
SELECT p.id, p.code, s.id AS stage_id, s.stage_order, s.is_mature
FROM world_cultivation_profiles p
JOIN world_cultivation_stages s ON s.cultivation_profile_id = p.id
WHERE p.id = 960400 ORDER BY s.stage_order;
SELECT seed_item_id, cultivation_profile_id, consume_count
FROM world_cultivation_planting_bindings
WHERE cultivation_profile_id = 960400;
SELECT rp.id, rs.id AS spawn_id, rs.zone_id
FROM world_resource_profiles rp JOIN world_resource_spawns rs
ON rs.resource_profile_id = rp.id WHERE rp.id = 960410;
PRAGMA foreign_key_check;
ROLLBACK;
-- CLIENT compact.db: exact mirror visual subset
PRAGMA foreign_keys = ON;
BEGIN IMMEDIATE;
INSERT INTO world_visual_assets (id, resource_kind, resource_path, scale, enabled)
VALUES
(960501, 1, 'Data/WorldInteraction/TEST_crop_seed.smc', 1.0, 0),
(960502, 1, 'Data/WorldInteraction/TEST_crop_mature.smc', 1.0, 0),
(960503, 1, 'Data/WorldInteraction/TEST_ore_active.smc', 1.0, 0),
(960504, 1, 'Data/WorldInteraction/TEST_ore_depleted.smc', 1.0, 0);
INSERT INTO world_object_state_visuals (object_template_id, state_id, visual_asset_id)
VALUES
(960200, 960201, 960501), (960200, 960202, 960502),
(960300, 960301, 960503), (960300, 960302, 960504);
UPDATE world_visual_catalog_metadata
SET catalog_revision = catalog_revision + 1 WHERE id = 1;
-- Verification SELECT:
SELECT v.object_template_id, v.state_id, v.visual_asset_id, a.resource_path
FROM world_object_state_visuals v
JOIN world_visual_assets a ON a.id = v.visual_asset_id
WHERE v.object_template_id IN (960200, 960300)
ORDER BY v.object_template_id, v.state_id;
PRAGMA foreign_key_check;
ROLLBACK;
Cleanup: resource spawn/profile, environment/seed/stages/profile, visual bindings/assets, states/templates, loot, actions/graph/targets/labor. Server и client visual cleanup выполнять в обеих copies; durable PostgreSQL entities предварительно не удалять вручную.
ATTACHEncrypted/package copies могут требовать разного tooling, поэтому не использовать случайный ATTACH одной production DB к другой. Выполнить одинаковые read-only SELECT отдельно на подготовленных server/client copies и сравнить выгрузки по ключу:
Target DB: отдельные read-only server и client compact.db copies; не соединять их через ATTACH.
Prerequisites: SQLCipher-compatible tooling и заранее выбранные реальные bind IDs.
Verification SELECT: весь блок является набором verification SELECT для object, cultivation/resource и housing identities.
Rollback/cleanup: не применимо — блок не начинает transaction и ничего не изменяет.
-- Read-only verification SELECT; no rollback/cleanup is applicable.
-- Client DB; для cultivation/resources тот же result set обязан быть и в server DB.
SELECT v.object_template_id, v.state_id, v.visual_asset_id,
a.resource_kind, a.resource_path
FROM world_object_state_visuals v
JOIN world_visual_assets a ON a.id = v.visual_asset_id
WHERE v.object_template_id IN (:template_id_1, :template_id_2)
ORDER BY v.object_template_id, v.state_id;
-- Server gameplay identity generic object (и основа проверки cultivation/resource).
SELECT object_template_id, id AS state_id, visual_asset_id
FROM world_interaction_object_states
WHERE object_template_id IN (:template_id_1, :template_id_2)
ORDER BY object_template_id, id;
-- Housing: выполнить на client DB и сопоставить IDs с server
-- world_building_stages/world_housing_decoration_templates.
SELECT building_template_id, stage_id, visual_asset_id, is_complete
FROM world_building_stage_visuals
WHERE building_template_id = :building_template_id
ORDER BY stage_id;
SELECT decoration_template_id, visual_asset_id
FROM world_housing_decoration_visuals
WHERE decoration_template_id = :decoration_template_id;
Для generic object server DB может не иметь mirror binding row, если template не относится к cultivation: тогда source of truth на server — world_interaction_object_states.visual_asset_id, а сравнивать надо его с client query. Для cultivation/resource mirror rows обязательны в обеих DB. Ни один из этих SELECT не проверяет наличие .smc/.mdl в package и не заменяет разрешённый впоследствии loader/runti