feat(core): add compact durable world storage and verified CLI
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Приёмка Shacraft Core
|
||||
|
||||
Статус: план проверок; ни один пункт пока не считается выполненным. Основа — исходный запрос пользователя и текущий `docs/CONTRACT.md`. Последний описывает первый рабочий протокол, но пока не покрывает весь согласованный MVP.
|
||||
Статус: план проверок; фактические результаты первого этапа находятся в `STATUS.md` и `VERIFICATION.md`. Рубежи A и B целиком ещё не пройдены. Основа — исходный запрос пользователя и текущий `docs/CONTRACT.md`. Последний описывает первый рабочий протокол, но пока не покрывает весь согласованный MVP.
|
||||
|
||||
Shacraft Core — самостоятельный открытый движок на Rust. Shacraft-сервер и Spleef проверяют его пригодность на практике. Экономия серверной RAM — основное архитектурное требование. Полный базовый каталог Minecraft 26.2 означает контент, состояния и формы, а не требование воспроизвести архитектуру Minecraft или весь ванильный игровой процесс.
|
||||
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
# Implementation contract, v1
|
||||
|
||||
Core Rust API (public in shacraft_core):
|
||||
Stage 1 core Rust API (public in shacraft_core):
|
||||
`type Pos = [i32;3]; type BlockId = u32;`
|
||||
`WorldStore::open(path: impl AsRef<Path>, cache_sections: usize) -> anyhow::Result<Self>`
|
||||
`list_worlds(&self) -> Result<Vec<WorldInfo>>`; `create_world(&mut self, name: &str, template: Option<&str>) -> Result<()>`; `reset_world(&mut self, name: &str, expected_revision: u64, operation_id: &str) -> Result<EditResult>`.
|
||||
@@ -10,6 +10,7 @@ Core Rust API (public in shacraft_core):
|
||||
`undo(&mut self, world: &str, expected_revision: u64, operation_id: &str, target_operation: &str) -> Result<EditResult>`.
|
||||
`read_region(&mut self, world: &str, min: Pos, max: Pos) -> Result<Vec<BlockChange>>` returns nonair cells, bounds inclusive, capped at 262144 cells.
|
||||
`stats(&self) -> serde_json::Value`; `flush(&mut self) -> Result<()>`.
|
||||
`collect_garbage(&mut self, max_blobs: usize) -> Result<usize>` deletes at most 1..4096 unreachable blobs per call, preserving snapshots and operation history. This limits deletions, not SQL scan latency, and does not vacuum the database file.
|
||||
`BlockChange { pub pos: Pos, pub block: BlockId }` derives Serialize/Deserialize/Clone.
|
||||
`EditResult { pub revision: u64, pub changed: usize, pub replayed: bool }` derives Serialize/Deserialize/Clone.
|
||||
`WorldInfo { pub name: String, pub revision: u64, pub template: Option<String> }` derives Serialize/Deserialize/Clone.
|
||||
@@ -20,14 +21,17 @@ Stage 1 storage semantics (accepted before implementation):
|
||||
- create_world(template) snapshots the source's current effective contents using references to immutable section blobs. The source's subsequent edits/reset never alter the child. Snapshot metadata stays disk indexed; creating the first snapshot can cost O(number of stored section references), without expanding all blocks in RAM.
|
||||
- reset returns the instance to its pinned base (or air for worlds without a base), increments revision monotonically, and has expected_revision/idempotency checks just like edit. A reset is a barrier for undo of older operations.
|
||||
- Operation IDs scoped per world, bounded UTF-8 strings. Look up idempotency before comparing the current revision. Repeating identical payload returns the stored result with replayed=true; same ID with a different payload is an error. Results survive restart and resets.
|
||||
- Every new successful edit (including changed=0) increments the revision; an exact replay does not. This is an explicit initial API policy, and no-op edits are also undo barriers.
|
||||
- Transactions publish and are acknowledged only after durable commit. Registry additions are durable. All input validation precedes mutation. Revisions fit SQLite's positive signed 64-bit integer range; reject overflow.
|
||||
- Undo is atomic and conflict aware. It may conservatively require current revision==the target edit's resulting revision (safe initial implementation, explicitly documented); selective nonoverlapping undo is a later extension. Never overwrite later writes or ABA cycles silently. Undoing an undo/reset is unsupported initially.
|
||||
- Default transaction limit 32768 cells; read_region limit 262144 volume; inclusive bounds and checked arithmetic. Reject duplicate cell positions in one edit, unknown BlockId, nonexistent world, malformed name, negative/overflowing volume and excessive input before writing.
|
||||
- World names match [A-Za-z0-9_-]{1,64}; block canonical names and registry cardinality are bounded. Minimum supported coordinate domain [-30000000,30000000] for every axis with checked validation. Air ID 0 cannot be reassigned. registry returns owned canonical identifiers shared by worlds; bounds and its RAM footprint are reported.
|
||||
- Section encoding versioned and defensively decoded: uniform or minimal-width palette + packed indices. Deduplicate immutable blobs; references are stored in indexed SQL tables. GC of unreachable blobs is explicit and bounded; disk retention is documented separately from RAM accounting.
|
||||
- Diagnostics distinguish decoded payload estimates, cache entries/capacity, SQLite cache configuration, registry bytes, disk data and measured OS RSS. Do not call estimated payload size total server RAM.
|
||||
- Implemented cache stores immutable encoded sections (uniform/palette/dense); `cache_payload_bytes` sums encoded lengths including their headers, excluding allocator/map overhead. `transient_decoded_payload_peak_estimate_bytes` counts temporary section-array payload, not total transient allocation. SQLite page cache budget is 4 MiB (SQLite setting, not a hard process RSS cap).
|
||||
- Implemented cardinality limits: registry 262144 states with at most 512 ASCII bytes per canonical state; worlds 10000; operation IDs 1..128 UTF-8 bytes without control characters. Registry strings reside in RAM and are accounted separately. Operation history is disk indexed and retained indefinitely in this initial release; explicit GC does not remove it.
|
||||
|
||||
Server protocol (JSON, WebSocket /ws):
|
||||
Planned Stage 2 server protocol (JSON, WebSocket /ws; not implemented in Stage 1):
|
||||
Client first: {type:'join',name:'Player',world:'lobby',manifest_hash:'…'}.
|
||||
Server welcome: {type:'welcome',id,world,revision,registry:[canonical block states],blocks:[{pos:[x,y,z],block:id}],players:[],spawn:[x,y,z],manifest_hash}.
|
||||
Client inputs at <=30Hz: {type:'input',seq,yaw,pitch,forward,strafe,jump}; yaw/pitch radians; forward/strafe in [-1,1].
|
||||
@@ -38,5 +42,5 @@ Server edit: {type:'blocks',revision,changes:[{pos,block}]}.
|
||||
Server error: {type:'error',message}; chat {type:'chat',name,text}.
|
||||
Client fetch GET /api/manifest -> {protocol:1,hash,packages:[],...}; GET /api/worlds -> [{name,revision,template}]; GET /api/catalog -> [{id,state,color,solid,shape}]. Server serves client from / .
|
||||
Control HTTP: GET /api/health, /api/metrics, /api/worlds, /api/catalog public; POST /api/control with Authorization: Bearer token and {method,params}, JSON return {result:...} or {error:'...'}.
|
||||
Control methods: world.list, world.create {name,template?}, world.reset {world}, world.read {world,min,max}, world.edit {world,expected_revision,operation_id,changes}, world.undo {world,expected_revision,operation_id,target_operation}, catalog.search {query,limit}, metrics, arena.start {world}, camera.capture {world,min?,max?} (top-down PNG), entity.spawn {world,kind,position}, entity.list {world}.
|
||||
Control methods: world.list, world.create {name,template?}, world.reset {world,expected_revision,operation_id}, world.read {world,min,max}, world.edit {world,expected_revision,operation_id,changes}, world.undo {world,expected_revision,operation_id,target_operation}, catalog.search {query,limit}, metrics, arena.start {world}, camera.capture {world,min?,max?} (top-down PNG), entity.spawn {world,kind,position}, entity.list {world}.
|
||||
Default server host 127.0.0.1 port 4000, token file data/control.token (0600). No publishing or production access.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Разработка и воспроизведение первого этапа
|
||||
|
||||
Это инструменты проверки хранилища. Здесь ещё нет игрового сервера, графического клиента или MCP. Локальный JSON-lines интерфейс CLI не является MCP.
|
||||
|
||||
## Сборка и проверки
|
||||
|
||||
Нужны Rust/Cargo 1.96.0, C-компилятор для bundled SQLite, Python 3 для проверки аварийного завершения. После загрузки зависимостей `Cargo.lock` фиксирует их версии.
|
||||
|
||||
```bash
|
||||
cd /home/emil/Desktop/shacraft-core
|
||||
bash scripts/verify.sh
|
||||
```
|
||||
|
||||
Проверка запускает форматирование, Clippy, Rust-тесты, сборку CLI и отдельные процессы. `check_storage.py` создаёт только временные миры и принудительно завершает только собственный дочерний процесс.
|
||||
|
||||
## Демонстрация хранения
|
||||
|
||||
```bash
|
||||
cargo run -p shacraft-tools -- --data data/demo demo
|
||||
cargo run -p shacraft-tools -- --data data/demo stats
|
||||
```
|
||||
|
||||
`demo` требует пустое хранилище. Создаёт карту пола и два независимых экземпляра, изменяет один, проверяет изоляцию, выполняет undo и reset. Повторный запуск в непустой каталог отклоняется. Для нового прогона укажите другое имя каталога.
|
||||
|
||||
## Измерение
|
||||
|
||||
```bash
|
||||
cargo build --release -p shacraft-tools
|
||||
target/release/shacraft-tools --data data/bench-001 --cache 8 benchmark --sections 256 --worlds 100
|
||||
```
|
||||
|
||||
`benchmark` также требует пустое хранилище. Он создаёт 256 различных секций, 100 экземпляров, проходит по данным сверх вместимости кэша и изменяет/сбрасывает каждый экземпляр. В JSON выводятся отдельные измерения RSS/пика всего процесса (на Linux), метрики хранения, длительности и размеры файлов. Данные теста синтетические; игроков, сетевого тика и фоновой симуляции нет. Эти числа не доказывают выигрыш относительно Paper.
|
||||
|
||||
## JSON-lines сессия
|
||||
|
||||
```bash
|
||||
target/debug/shacraft-tools --data data/manual --cache 8 session
|
||||
```
|
||||
|
||||
По одной JSON-команде на строку:
|
||||
|
||||
```json
|
||||
{"op":"register","state":"shacraft:stone"}
|
||||
{"op":"create","name":"world"}
|
||||
{"op":"registry"}
|
||||
{"op":"revision","world":"world"}
|
||||
{"op":"get","world":"world","pos":[-1,0,0]}
|
||||
{"op":"stats"}
|
||||
```
|
||||
|
||||
Сначала получите реальный ID блока и текущую ревизию, затем передайте их в `edit`:
|
||||
|
||||
```json
|
||||
{"op":"edit","world":"world","expected_revision":0,"operation_id":"first-stone","changes":[{"pos":[-1,0,0],"block":1}]}
|
||||
{"op":"undo","world":"world","expected_revision":1,"operation_id":"undo-first","target_operation":"first-stone"}
|
||||
{"op":"reset","world":"world","expected_revision":2,"operation_id":"reset-world"}
|
||||
```
|
||||
|
||||
ID 1 в этом примере допустим только если ответ регистрации действительно вернул 1. Ответ каждой команды имеет `ok` и `result` либо `error`. Успешный ответ записи выдаётся после возврата долговечного API. Максимальная входная строка — 8 MiB; ограничения на число блоков и объём области действуют дополнительно.
|
||||
|
||||
## Продолжение разработки
|
||||
|
||||
Прочитайте STATUS и PLAN. Новые компоненты добавляйте в workspace только вместе с реализацией и командами проверки. Не создавайте пустые исполняемые файлы, которые лишь выводят «готово». Публичный протокол пока проектируется в CONTRACT; окончание первого этапа хранения не закрывает последующие этапы.
|
||||
|
||||
Для живого SQLite-хранилища нельзя считать копию одного `worlds.sqlite3` полной резервной копией: актуальные данные могут быть в WAL. Перед ручным копированием остановите владеющий процесс либо используйте будущий согласованный backup API. Исходный архив проекта намеренно не содержит рабочие миры.
|
||||
@@ -78,7 +78,7 @@
|
||||
Одна успешная мутация атомарно фиксирует:
|
||||
|
||||
1. Новые секции и изменения ссылок мира.
|
||||
2. Новую ревизию, если состояние мира изменилось.
|
||||
2. Новую ревизию принятой операции, включая no-op.
|
||||
3. Запись операции, её отпечаток и точный результат для повторной выдачи.
|
||||
4. Данные истории, необходимые для безопасного undo.
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
Отпечаток включает метод и все его значимые аргументы, включая `expected_revision`; порядок уникальных позиций канонизируется. Область уникальности идентификатора — стабильный внутренний ID мира, а не имя, которое в будущем может быть переиспользовано.
|
||||
|
||||
Для операции, реально не изменяющей блоки, предлагается сохранять идемпотентный результат с `changed=0` без увеличения ревизии. Пустые и повторные запросы не должны бесконечно увеличивать номер состояния. Такая операция всё же требует долговечной записи дедупликации.
|
||||
Принятая семантика первого этапа: каждая новая успешно принятая edit-операция повышает ревизию, даже при `changed=0`. Такая операция также ставит границу для консервативного undo и сохраняет долговечный результат дедупликации. Точный повтор операции не увеличивает ревизию.
|
||||
|
||||
Повтор возвращает ревизию исходной операции, которая может быть меньше текущей. Клиент не должен откатывать свою текущую ревизию по такому ответу. Ошибка после commit, но до доставки ответа, означает неопределённость для вызывающего кода: безопасный повтор использует тот же идентификатор и те же аргументы.
|
||||
|
||||
|
||||
+2
-1
@@ -2,6 +2,8 @@
|
||||
|
||||
План составлен до реализации. Каждый этап заканчивается воспроизводимым результатом в локальном репозитории. `docs/STATUS.md` обновляется после проверок. Пункт не считается выполненным по наличию интерфейса, заглушки, каталога имён или успешной компиляции.
|
||||
|
||||
Текущий прогресс: этап 0 и первая реализация этапа 1 завершены. Проверены библиотека хранения, CLI и аварийное восстановление. Этапы 2–6 остаются впереди; подробности в STATUS и VERIFICATION.
|
||||
|
||||
## Этап 0. Сохранить контекст и проверить проектирование
|
||||
|
||||
- Записать исходные требования, решения, контракты и критерии приёмки.
|
||||
@@ -76,4 +78,3 @@
|
||||
## Рабочий порядок после планирования
|
||||
|
||||
Сначала этап 1. Параллельно с ним можно готовить протокол и клиент по согласованному контракту, изучать каталог/конвертер. Подключение этих частей, изменения общих интерфейсов и проверки выполняются последовательно. После каждого этапа — сохранить результат; не оставлять единственную копию в одноразовой среде.
|
||||
|
||||
|
||||
+34
-10
@@ -1,19 +1,43 @@
|
||||
# Состояние проекта
|
||||
|
||||
Обновлено: 2026-09-14.
|
||||
Обновлено: 2026-09-14. Рабочая папка: `/home/emil/Desktop/shacraft-core`.
|
||||
|
||||
## Подтверждено локально
|
||||
## Завершено: планирование и первый этап хранения
|
||||
|
||||
- Создан отдельный Git-репозиторий `/home/emil/Desktop/shacraft-core`.
|
||||
- Доступны Rust 1.96.0, Cargo 1.96.0, Node.js 22.22.3, Python 3.14.4.
|
||||
- Созданы workspace manifest, директории компонентов и первоначальный контракт. Компилируемой реализации пока нет.
|
||||
- Записаны требования, план и решения. Независимо проверяются план хранения, совместимость и критерии приёмки.
|
||||
План сохранён **до реализации**, отдельным Git-коммитом `ddfcef2`. Исходники старой облачной среды не восстановлены и не использованы как доказательство работы новой реализации.
|
||||
|
||||
## Ближайшие действия
|
||||
Работают две crate:
|
||||
|
||||
1. Завершить проверку документов и поправить контракт.
|
||||
2. Реализовать и проверить библиотеку ядра (этап 1).
|
||||
3. Добавить сервер/клиент и собственный MCP с проверкой сквозного сценария.
|
||||
- `shacraft-core`: versioned codec секций 16³ (uniform/palette/dense), компактный LRU, долговечный реестр, SQLite/WAL/FULL, блокировка второго писателя, неизменяемые снимки, независимые экземпляры, атомарные edit, revision/idempotency, undo/reset и явная уборка недостижимых blobs.
|
||||
- `shacraft-tools`: локальная JSON-lines сессия для тестирования API, demo, stats и воспроизводимый storage benchmark. Это не MCP и не игровой сервер.
|
||||
|
||||
Проверено на Linux x86_64 с Rust 1.96.0:
|
||||
|
||||
- `cargo fmt` и Clippy с запретом предупреждений.
|
||||
- 29 Rust-тестов: 11 codec, 8 black-box контрактов, 10 хранения.
|
||||
- Отдельные процессы: 80 подтверждённых правок → SIGKILL → восстановление реестра/данных/replay; прерывание транзакции на 32768 ячеек → отсутствие частичного состояния → безопасный повтор.
|
||||
- Вытеснение различных секций при превышении кэша; однородная секция остаётся компактной и в RAM.
|
||||
- Release demo и синтетический benchmark: 256 секций, 100 независимых экземпляров, чтение сверх кэша, edit/reset каждого экземпляра.
|
||||
|
||||
Команды и результаты: [DEVELOPMENT](DEVELOPMENT.md), [VERIFICATION](VERIFICATION.md), `docs/verification/`.
|
||||
|
||||
## Следующий этап: сервер и тестовый клиент
|
||||
|
||||
1. Добавить crate сервера с игровым циклом отдельно от долговечных редакторских транзакций. Не выполнять SQL/FULL commit каждого игрового ввода.
|
||||
2. Уточнить протокол resync, подписку без разрыва между снимком и изменениями, переключение миров и лимиты очередей.
|
||||
3. Реализовать серверные движение/коллизии/взаимодействия и минимальный WebGL2-клиент по решению D003.
|
||||
4. Пройти сценарий двух клиентов с общими блоками и независимыми мирами.
|
||||
5. Затем подключить собственный MCP через общий Control API, полный каталог и конвертер согласно PLAN.
|
||||
|
||||
## Ограничения первого этапа
|
||||
|
||||
- Полный MVP ещё не завершён. Нет игрового сервера, графического клиента, MCP, каталога 26.2, конвертера, системы исполняемых модулей и миниигры.
|
||||
- Undo консервативный: только целевая правка на текущей ревизии, без последующих операций. Каждый новый успешный edit, включая no-op, повышает ревизию. Retry не повышает.
|
||||
- История остаётся на диске без автоматического удаления. GC ограничивает число удалённых blobs, но не время сканирования. Он не удаляет историю/снимки и не уменьшает физический файл через VACUUM.
|
||||
- Реестр строк находится в RAM: максимум 262144 состояния по 512 ASCII-байт. Кэш секций не является лимитом всей памяти процесса. SQLite page-cache budget — настройка движка, не жёсткий RSS-лимит.
|
||||
- Benchmark проверяет хранение, не игроков/тики/полный каталог и не сравнительную экономию относительно Paper.
|
||||
- SIGKILL не имитирует отключение питания и отказы накопителя. Платформы вне Linux пока не проверены.
|
||||
- Продакшен, существующий лаунчер и удалённые репозитории не изменялись.
|
||||
|
||||
## Правила продолжения
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Проверки первого этапа, 14 сентября 2026
|
||||
|
||||
Проверены библиотека хранения и CLI. Рубеж A из ACCEPTANCE.md целиком ещё не пройден: сервер/клиент/MCP отсутствуют. Результаты ниже не объявляют полный MVP готовым.
|
||||
|
||||
## Автоматические проверки
|
||||
|
||||
Команда `bash scripts/verify.sh` завершилась с кодом 0. В [полном журнале](verification/checks.txt) сохранены:
|
||||
|
||||
- форматирование и Clippy `--workspace --all-targets --locked -- -D warnings` без ошибок;
|
||||
- 29 Rust-тестов (11 секционного формата, 8 публичных контрактов, 10 хранилища);
|
||||
- сборка CLI;
|
||||
- девять проверок отдельного процесса, включая принудительное завершение и восстановление.
|
||||
|
||||
В аварийном сценарии 80 правок были подтверждены процессом до SIGKILL и сохранились после повторного открытия. Повтор старой операции вернул её исходный результат без повторной правки. Большая операция на 32768 блоков, завершения которой вызывающий код не наблюдал, восстановилась целиком или отсутствовала целиком; безопасный повтор завершился на одной ревизии. Дополнительно SQL-тест принудительно вызывает отказ в середине изменения и проверяет откат данных, истории и ревизии.
|
||||
|
||||
## Проверка работы и хранения
|
||||
|
||||
`cargo build --release -p shacraft-tools --locked` завершилась успешно. [Результат demo](verification/demo.json) подтверждает сценарий общей карты, двух экземпляров, изолированной правки, undo и reset.
|
||||
|
||||
[Сырой результат benchmark](verification/storage-benchmark.json) получен командой:
|
||||
|
||||
```bash
|
||||
target/release/shacraft-tools --data <пустой-временный-каталог> --cache 8 benchmark --sections 256 --worlds 100
|
||||
```
|
||||
|
||||
После создания 100 экземпляров количество blobs осталось 256: карты разделяют неизменяемые данные. После обхода карты резидентный кэш остался в пределах восьми записей и зафиксировал вытеснения. Все экземпляры прошли независимую правку и сброс. RSS, размеры файлов и длительности сохранены в JSON вместе с версией Rust и системой.
|
||||
|
||||
Это один синтетический прогон на небольшом реестре из двух состояний, без сети, игроков и симуляции. Не использовать его для оценки готового сервера или сравнения с Paper. Байты закодированных секций не включают служебные структуры/аллокатор; RSS измеряется отдельно.
|
||||
|
||||
## Ограничения доказательств
|
||||
|
||||
Подтверждена работа на Linux x86_64. Тесты завершения процесса не имитируют отключение питания или аппаратные сбои. Часть защиты от повреждений проверяется на намеренно изменённых временных SQLite-базах; реальные миры пользователя не затрагиваются. Интеграция с Minecraft, лаунчером и MCP относится к следующим этапам и пока не проверялась.
|
||||
@@ -0,0 +1,123 @@
|
||||
Checking shacraft-core v0.1.0 (/home/emil/Desktop/shacraft-core/crates/shacraft-core)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
|
||||
Compiling shacraft-core v0.1.0 (/home/emil/Desktop/shacraft-core/crates/shacraft-core)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.46s
|
||||
Running unittests src/lib.rs (target/debug/deps/shacraft_core-20405a893c25a7e7)
|
||||
|
||||
running 11 tests
|
||||
test section::tests::direct_read_rejects_out_of_range_cell - should panic ... ok
|
||||
test section::tests::crc32_matches_standard_check_value ... ok
|
||||
test section::tests::packed_indices_match_cross_byte_wire_fixture ... ok
|
||||
test section::tests::uniform_round_trip_and_little_endian_layout ... ok
|
||||
test section::tests::invalid_palettes_and_indices_are_rejected ... ok
|
||||
test section::tests::structurally_invalid_headers_are_rejected_even_with_valid_checksum ... ok
|
||||
test section::tests::every_storage_mode_checks_registry_bounds ... ok
|
||||
test section::tests::dense_round_trip_and_size_crossover ... ok
|
||||
test section::tests::truncated_extra_and_corrupted_bytes_are_rejected ... ok
|
||||
test section::tests::palette_width_changes_at_powers_of_two ... ok
|
||||
test section::tests::every_palette_width_round_trips ... ok
|
||||
|
||||
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
|
||||
|
||||
Running tests/contract.rs (target/debug/deps/contract-fb06222454ea80ae)
|
||||
|
||||
running 8 tests
|
||||
test accepted_noop_advances_revision_and_prevents_undo_of_earlier_edit ... ok
|
||||
test edit_replay_precedes_revision_check_and_survives_reset_and_reopen ... ok
|
||||
test operation_id_collision_rejects_changed_payload_across_restart_and_methods ... ok
|
||||
test reset_is_monotonic_idempotent_durable_and_an_undo_barrier ... ok
|
||||
test invalid_batch_never_partially_writes_or_advances_revision ... ok
|
||||
test undo_restores_inherited_content_and_replays_without_mutating_newer_state ... ok
|
||||
test strict_undo_rejects_later_nonoverlapping_edits_and_aba_cycles ... ok
|
||||
test snapshots_pin_effective_source_contents_and_air_overrides ... ok
|
||||
|
||||
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
|
||||
|
||||
Running tests/storage.rs (target/debug/deps/storage-2b641b21f5b18d8e)
|
||||
|
||||
running 10 tests
|
||||
test registry_is_canonical_durable_and_rejects_malformed_identifiers ... ok
|
||||
test lock_is_exclusive_and_released_on_drop ... ok
|
||||
test corrupted_blob_is_reported_instead_of_air ... ok
|
||||
test revision_overflow_rejects_mutation_but_keeps_original_replay ... ok
|
||||
test explicit_gc_preserves_snapshots_history_and_cache_correctness ... ok
|
||||
test sqlite_failure_mid_edit_rolls_back_blobs_history_and_revision ... ok
|
||||
test oversized_unreachable_blob_is_rejected_before_reinterning ... ok
|
||||
test compact_cache_evicts_distinct_blobs_and_shares_identical_sections ... ok
|
||||
test coordinate_boundaries_and_inclusive_read_limits ... ok
|
||||
test uniform_section_remains_twenty_bytes_in_resident_cache ... ok
|
||||
|
||||
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
|
||||
|
||||
Running unittests src/main.rs (target/debug/deps/shacraft_tools-7c7bfbbbef840de7)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||
|
||||
Doc-tests shacraft_core
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
|
||||
{
|
||||
"passed": true,
|
||||
"checks": [
|
||||
"bounded resident cache with verified eviction while reading 80 sections",
|
||||
"second writer rejected while owner is running",
|
||||
"80 acknowledged edits and registry survive SIGKILL without flush",
|
||||
"idempotent replay after SIGKILL precedes current revision check",
|
||||
"invalid batch leaves both blocks and revision unchanged",
|
||||
"interrupted 32768-cell transaction is atomic (recovered revision 0)",
|
||||
"uncertain interrupted request can safely be retried",
|
||||
"invalid request does not terminate the local test session",
|
||||
"4096-cell uniform section occupies 20 encoded payload bytes in resident cache"
|
||||
],
|
||||
"final_metrics": {
|
||||
"process": {
|
||||
"peak_rss_bytes": 8237056,
|
||||
"rss_bytes": 8237056,
|
||||
"source": "/proc/self/status (null when unavailable)"
|
||||
},
|
||||
"storage": {
|
||||
"cache_capacity_sections": 2,
|
||||
"cache_entries": 1,
|
||||
"cache_evictions": 0,
|
||||
"cache_hits": 0,
|
||||
"cache_misses": 1,
|
||||
"cache_payload_bytes": 20,
|
||||
"cache_representation": "encoded_uniform_palette_dense",
|
||||
"commits_this_handle": 0,
|
||||
"coordinate_limit": 30000000,
|
||||
"database_bytes": 647168,
|
||||
"history_change_count": 36944,
|
||||
"history_retention": "all_operations_until_explicit_future_retention_policy",
|
||||
"immutable_blob_count": 18,
|
||||
"last_mutation_duration_us": 0,
|
||||
"max_edit_cells": 32768,
|
||||
"max_read_volume": 262144,
|
||||
"operation_count": 82,
|
||||
"os_rss_bytes": null,
|
||||
"overlay_section_count": 97,
|
||||
"registry_count": 2,
|
||||
"registry_limit_states": 262144,
|
||||
"registry_text_bytes": 32,
|
||||
"schema_version": 1,
|
||||
"shm_bytes": 32768,
|
||||
"snapshot_count": 0,
|
||||
"sqlite_cache_budget_bytes": 4194304,
|
||||
"sqlite_journal_mode": "wal",
|
||||
"sqlite_synchronous": "FULL",
|
||||
"transient_decoded_payload_peak_estimate_bytes": 16384,
|
||||
"wal_bytes": 0,
|
||||
"world_count": 3,
|
||||
"world_limit": 10000
|
||||
}
|
||||
},
|
||||
"limits": [
|
||||
"process crash tested; power loss and faulty hardware are not simulated",
|
||||
"storage-only workload; no Paper or gameplay performance comparison"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"edit": {
|
||||
"changed": 1,
|
||||
"replayed": false,
|
||||
"revision": 1
|
||||
},
|
||||
"metrics": {
|
||||
"process": {
|
||||
"peak_rss_bytes": 6131712,
|
||||
"rss_bytes": 6131712,
|
||||
"source": "/proc/self/status (null when unavailable)"
|
||||
},
|
||||
"storage": {
|
||||
"cache_capacity_sections": 64,
|
||||
"cache_entries": 2,
|
||||
"cache_evictions": 0,
|
||||
"cache_hits": 3,
|
||||
"cache_misses": 2,
|
||||
"cache_payload_bytes": 1076,
|
||||
"cache_representation": "encoded_uniform_palette_dense",
|
||||
"commits_this_handle": 8,
|
||||
"coordinate_limit": 30000000,
|
||||
"database_bytes": 77824,
|
||||
"history_change_count": 1026,
|
||||
"history_retention": "all_operations_until_explicit_future_retention_policy",
|
||||
"immutable_blob_count": 2,
|
||||
"last_mutation_duration_us": 99,
|
||||
"max_edit_cells": 32768,
|
||||
"max_read_volume": 262144,
|
||||
"operation_count": 4,
|
||||
"os_rss_bytes": null,
|
||||
"overlay_section_count": 4,
|
||||
"registry_count": 2,
|
||||
"registry_limit_states": 262144,
|
||||
"registry_text_bytes": 27,
|
||||
"schema_version": 1,
|
||||
"shm_bytes": 32768,
|
||||
"snapshot_count": 1,
|
||||
"sqlite_cache_budget_bytes": 4194304,
|
||||
"sqlite_journal_mode": "wal",
|
||||
"sqlite_synchronous": "FULL",
|
||||
"transient_decoded_payload_peak_estimate_bytes": 32768,
|
||||
"wal_bytes": 0,
|
||||
"world_count": 3,
|
||||
"world_limit": 10000
|
||||
}
|
||||
},
|
||||
"reset": {
|
||||
"changed": 0,
|
||||
"replayed": false,
|
||||
"revision": 3
|
||||
},
|
||||
"scenario": "shared floor -> isolated edit -> undo -> reset",
|
||||
"undo": {
|
||||
"changed": 1,
|
||||
"replayed": false,
|
||||
"revision": 2
|
||||
},
|
||||
"worlds": [
|
||||
{
|
||||
"name": "arena_a",
|
||||
"revision": 3,
|
||||
"template": "template"
|
||||
},
|
||||
{
|
||||
"name": "arena_b",
|
||||
"revision": 0,
|
||||
"template": "template"
|
||||
},
|
||||
{
|
||||
"name": "template",
|
||||
"revision": 1,
|
||||
"template": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"after_access_edit_reset": {
|
||||
"process": {
|
||||
"peak_rss_bytes": 7471104,
|
||||
"rss_bytes": 7471104,
|
||||
"source": "/proc/self/status (null when unavailable)"
|
||||
},
|
||||
"storage": {
|
||||
"cache_capacity_sections": 8,
|
||||
"cache_entries": 8,
|
||||
"cache_evictions": 249,
|
||||
"cache_hits": 299,
|
||||
"cache_misses": 257,
|
||||
"cache_payload_bytes": 4304,
|
||||
"cache_representation": "encoded_uniform_palette_dense",
|
||||
"commits_this_handle": 558,
|
||||
"coordinate_limit": 30000000,
|
||||
"database_bytes": 1343488,
|
||||
"history_change_count": 65380,
|
||||
"history_retention": "all_operations_until_explicit_future_retention_policy",
|
||||
"immutable_blob_count": 257,
|
||||
"last_mutation_duration_us": 108,
|
||||
"max_edit_cells": 32768,
|
||||
"max_read_volume": 262144,
|
||||
"operation_count": 456,
|
||||
"os_rss_bytes": null,
|
||||
"overlay_section_count": 256,
|
||||
"registry_count": 2,
|
||||
"registry_limit_states": 262144,
|
||||
"registry_text_bytes": 27,
|
||||
"schema_version": 1,
|
||||
"shm_bytes": 32768,
|
||||
"snapshot_count": 1,
|
||||
"sqlite_cache_budget_bytes": 4194304,
|
||||
"sqlite_journal_mode": "wal",
|
||||
"sqlite_synchronous": "FULL",
|
||||
"transient_decoded_payload_peak_estimate_bytes": 32768,
|
||||
"wal_bytes": 0,
|
||||
"world_count": 101,
|
||||
"world_limit": 10000
|
||||
}
|
||||
},
|
||||
"after_forks": {
|
||||
"process": {
|
||||
"peak_rss_bytes": 7446528,
|
||||
"rss_bytes": 7446528,
|
||||
"source": "/proc/self/status (null when unavailable)"
|
||||
},
|
||||
"storage": {
|
||||
"cache_capacity_sections": 8,
|
||||
"cache_entries": 0,
|
||||
"cache_evictions": 0,
|
||||
"cache_hits": 0,
|
||||
"cache_misses": 0,
|
||||
"cache_payload_bytes": 0,
|
||||
"cache_representation": "encoded_uniform_palette_dense",
|
||||
"commits_this_handle": 358,
|
||||
"coordinate_limit": 30000000,
|
||||
"database_bytes": 1331200,
|
||||
"history_change_count": 65280,
|
||||
"history_retention": "all_operations_until_explicit_future_retention_policy",
|
||||
"immutable_blob_count": 256,
|
||||
"last_mutation_duration_us": 44,
|
||||
"max_edit_cells": 32768,
|
||||
"max_read_volume": 262144,
|
||||
"operation_count": 256,
|
||||
"os_rss_bytes": null,
|
||||
"overlay_section_count": 256,
|
||||
"registry_count": 2,
|
||||
"registry_limit_states": 262144,
|
||||
"registry_text_bytes": 27,
|
||||
"schema_version": 1,
|
||||
"shm_bytes": 32768,
|
||||
"snapshot_count": 1,
|
||||
"sqlite_cache_budget_bytes": 4194304,
|
||||
"sqlite_journal_mode": "wal",
|
||||
"sqlite_synchronous": "FULL",
|
||||
"transient_decoded_payload_peak_estimate_bytes": 32768,
|
||||
"wal_bytes": 1095952,
|
||||
"world_count": 101,
|
||||
"world_limit": 10000
|
||||
}
|
||||
},
|
||||
"after_template": {
|
||||
"process": {
|
||||
"peak_rss_bytes": 7442432,
|
||||
"rss_bytes": 7442432,
|
||||
"source": "/proc/self/status (null when unavailable)"
|
||||
},
|
||||
"storage": {
|
||||
"cache_capacity_sections": 8,
|
||||
"cache_entries": 0,
|
||||
"cache_evictions": 0,
|
||||
"cache_hits": 0,
|
||||
"cache_misses": 0,
|
||||
"cache_payload_bytes": 0,
|
||||
"cache_representation": "encoded_uniform_palette_dense",
|
||||
"commits_this_handle": 258,
|
||||
"coordinate_limit": 30000000,
|
||||
"database_bytes": 1232896,
|
||||
"history_change_count": 65280,
|
||||
"history_retention": "all_operations_until_explicit_future_retention_policy",
|
||||
"immutable_blob_count": 256,
|
||||
"last_mutation_duration_us": 675,
|
||||
"max_edit_cells": 32768,
|
||||
"max_read_volume": 262144,
|
||||
"operation_count": 256,
|
||||
"os_rss_bytes": null,
|
||||
"overlay_section_count": 256,
|
||||
"registry_count": 2,
|
||||
"registry_limit_states": 262144,
|
||||
"registry_text_bytes": 27,
|
||||
"schema_version": 1,
|
||||
"shm_bytes": 32768,
|
||||
"snapshot_count": 0,
|
||||
"sqlite_cache_budget_bytes": 4194304,
|
||||
"sqlite_journal_mode": "wal",
|
||||
"sqlite_synchronous": "FULL",
|
||||
"transient_decoded_payload_peak_estimate_bytes": 32768,
|
||||
"wal_bytes": 1095952,
|
||||
"world_count": 1,
|
||||
"world_limit": 10000
|
||||
}
|
||||
},
|
||||
"comparison": "no Paper baseline measured",
|
||||
"elapsed_ms": 257,
|
||||
"fork_ms": 5,
|
||||
"forked_worlds": 100,
|
||||
"scenario": "storage-only; no players, rendering or tick simulation",
|
||||
"sections": 256,
|
||||
"seed_ms": 201,
|
||||
"environment": {
|
||||
"system": "Linux",
|
||||
"kernel": "7.0.0-31-generic",
|
||||
"arch": "x86_64",
|
||||
"rust": "rustc 1.96.0 (ac68faa20 2026-05-25)",
|
||||
"build": "release",
|
||||
"date": "2026-09-14"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user