43 lines
6.1 KiB
Markdown
43 lines
6.1 KiB
Markdown
# Implementation contract, v1
|
|
|
|
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>`.
|
|
`register_block(&mut self, canonical_state: &str) -> Result<BlockId>`; `registry(&self) -> &[String]` (air is 0).
|
|
`get_block(&mut self, world: &str, pos: Pos) -> Result<BlockId>`; `revision(&self, world: &str) -> Result<u64>`.
|
|
`edit(&mut self, world: &str, expected_revision: u64, operation_id: &str, changes: Vec<BlockChange>) -> Result<EditResult>`.
|
|
`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<()>`.
|
|
`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.
|
|
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.
|