# Implementation contract, v1 Core Rust API (public in shacraft_core): `type Pos = [i32;3]; type BlockId = u32;` `WorldStore::open(path: impl AsRef, cache_sections: usize) -> anyhow::Result` `list_worlds(&self) -> Result>`; `create_world(&mut self, name: &str, template: Option<&str>) -> Result<()>`; `reset_world(&mut self, name: &str, expected_revision: u64, operation_id: &str) -> Result`. `register_block(&mut self, canonical_state: &str) -> Result`; `registry(&self) -> &[String]` (air is 0). `get_block(&mut self, world: &str, pos: Pos) -> Result`; `revision(&self, world: &str) -> Result`. `edit(&mut self, world: &str, expected_revision: u64, operation_id: &str, changes: Vec) -> Result`. `undo(&mut self, world: &str, expected_revision: u64, operation_id: &str, target_operation: &str) -> Result`. `read_region(&mut self, world: &str, min: Pos, max: Pos) -> Result>` returns nonair cells, bounds inclusive, capped at 262144 cells. `stats(&self) -> serde_json::Value`; `flush(&mut self) -> Result<()>`. `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 }` derives Serialize/Deserialize/Clone. All durable changes atomic/recoverable; bounded section cache and disk operation history; independent forks pin immutable templates. Core has no networking/rendering. Stage 1 storage semantics (accepted before implementation): - SQLite transactions with WAL and synchronous=FULL are preferred to an untested bespoke durability journal. Use a bounded SQLite cache and a bounded decoded section LRU; report both. Reject a second writer using an exclusive advisory lock file. Reject cache_sections=0 explicitly. - 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. - 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. Server protocol (JSON, WebSocket /ws): 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]. Coordinates: +Y up; yaw=0 looks toward -Z; +X right. Position is feet. Client actions: {type:'break',pos:[x,y,z]} or {type:'place',pos:[x,y,z],block:id}; {type:'switch_world',world}; {type:'chat',text}. Server tick 20Hz: {type:'state',players:[{id,name,position:[x,y,z],yaw,pitch}],tick,ack:seq,match:{phase,remaining,winner}}. 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}. Default server host 127.0.0.1 port 4000, token file data/control.token (0600). No publishing or production access.