Expand voxel gameplay, lighting, full-height streaming and world imports
MVP checks / mvp (push) Canceled after 0s
MVP checks / mvp (push) Canceled after 0s
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
This commit is contained in:
+18
-6
@@ -17,9 +17,9 @@ The first launch creates a lobby, the `gallery` world, an immutable Spleef base,
|
||||
|
||||
## State ownership
|
||||
|
||||
One dedicated thread owns WorldStore, physics, and game state. HTTP and WebSocket handlers enqueue bounded commands. Movement runs at 20 ticks per second: speed is 5 blocks/s, gravity is 20 blocks/s², and the normal jump impulse is 7 blocks/s. The player's AABB is 0.6×1.8 blocks; the position specifies the center of their feet. +Y points up, yaw increases to the right, and pitch increases upward; the viewing direction is `[sin(yaw)*cos(pitch),sin(pitch),-cos(yaw)*cos(pitch)]`.
|
||||
One dedicated thread owns WorldStore, physics, and game state. HTTP and WebSocket handlers enqueue bounded commands. The shared Rust/WASM movement solver runs at 20 ticks per second with measured Java 26.2 acceleration, friction, gravity, jump, fluid and collision behavior. Position specifies the center of the player's feet. Standing, crouching and swimming/crawling use different body and eye heights. +Y points up, yaw increases to the right, and pitch increases upward; the viewing direction is `[sin(yaw)*cos(pitch),sin(pitch),-cos(yaw)*cos(pitch)]`. See [player physics](PHYSICS.md) for reference evidence and limits.
|
||||
|
||||
Gameplay positions and spawn points are limited to ±32,700 on each axis because this client's physics uses `f32`. Storage and the converter retain their wider ±30,000,000 contract; this does not guarantee gameplay physics at distant coordinates. A player who leaves the gameplay range returns to spawn, or is eliminated during an active match.
|
||||
Player positions, spawn points, physics and action rays use double precision. Procedural worlds support horizontal gameplay through ±29,999,872 and build layers Y=−64…319; section-local rendering preserves distant geometry. Nonprocedural arena configuration and entity administration retain their ±32,700 bounds. Storage and conversion retain their ±30,000,000 contract. See [procedural worlds and diagnostics](WORLD_STREAMING.md) for the bounded CPU worker pool, durable natural baselines and streaming limits.
|
||||
|
||||
Input expresses movement intent rather than position. The server checks the 6-block reach, the nearest shape intersection, the placement cell, intersections with players, and arena rules. Stale input is cleared after one second. Gameplay physics reads a bounded region of neighboring blocks for each tick; independent worlds without players need no array of loaded sections.
|
||||
|
||||
@@ -35,15 +35,27 @@ The token is not secret from the local machine's administrator. The manifest has
|
||||
|
||||
## WebSocket, snapshots, and edits
|
||||
|
||||
The current client prefers **`chunk_stream_v2`** with **`view_buffer_v1`**, documented in [section protocol v2](WORLD_STREAMING.md#section-protocol-v2). It receives an incremental view (405 sections by default, configurable from 245 to 1,805) and palette/RLE batches with generation IDs. The loaded radius includes one extra chunk ring; horizontal view anchors move only after a two-chunk displacement. Older v2 clients without the buffer feature retain 245 sections by default, configurable from 125 to 1,445. Procedural worlds require v2. The descriptions below of full snapshots and `chunks` apply to the retained v1/legacy paths.
|
||||
|
||||
Send the first message to `/ws` within 10 seconds:
|
||||
|
||||
```json
|
||||
{"type":"join","protocol":1,"manifest_hash":"from /api/manifest","name":"Player","world":"lobby"}
|
||||
{"type":"join","protocol":1,"manifest_hash":"from /api/manifest","name":"Player","world":"lobby","features":["chunk_stream_v1","movement_prediction_v1"]}
|
||||
```
|
||||
|
||||
`welcome` contains `id`, `world`, `revision`, `blocks:[{pos,block}]`, definitions of the `materials` in use, `players`, `entities`, `spawn`, `view_center`, and `manifest_hash`. A snapshot covers `[centerX-32, centerY-8, centerZ-32]…[centerX+31, centerY+31, centerZ+31]`, with inclusive upper bounds. When the view center moves, the server sends a new `snapshot`; the client replaces its geometry completely. Snapshots do not include the full string registry. `materials` is limited to 256 entries and 128 KiB; the client requests the remaining definitions sequentially through `/api/catalog?ids=1,2,...&limit=128`. This allows players to join worlds with large palettes without overflowing the outbound queue.
|
||||
`welcome` contains `id`, `world`, `revision`, `blocks:[{pos,block}]`, definitions of the `materials` in use, `players`, `entities`, `spawn`, `view_center`, and `manifest_hash`. The view center uses world coordinates rounded down to multiples of 16, including negative coordinates. A client requesting `chunk_stream_v1` receives that feature in `features`, plus inclusive `view_min` and `view_max` bounds. Its full snapshot covers `[centerX-32, centerY-16, centerZ-32]…[centerX+31, centerY+31, centerZ+31]`: 4×3×4 complete 16³ sections.
|
||||
|
||||
The client sends `input` with `seq,yaw,pitch,forward,strafe,jump`; `break` with `pos`; `place` with `pos,block` or `state`; and `switch_world`, `resync`, `respawn`, `start_match`, `chat`, and `ping`. A `state` response contains authoritative positions, `tick`, the acknowledged input `ack`, and match state. Blocks arrive in `blocks` messages with a new revision. If a revision is missing, the client requests a snapshot. Subscription and snapshot creation are serialized with edits on the same thread, so an edit made between those steps cannot be lost.
|
||||
As the negotiated client's center moves, the server sends `chunks` updates at most once every 250 ms. Each contains `world`, the current `revision`, `from_center`, `view_center`, `view_min`, `view_max`, `unload:[[sectionX,sectionY,sectionZ]]`, `sections:[{section:[sectionX,sectionY,sectionZ],blocks:[{pos,block}]}]`, and `materials`. Section addresses are integer section coordinates; block positions and view centers are world coordinates. Only newly visible sections are read and sent, including empty ones, while retained sections keep their existing client geometry. Unloaded sections are removed. A jump with no view overlap sends every entering section through the same message. Chunk messages do not change the spawn, players, or world revision: they contain a view of the current revision. Initial joins, world switches, and explicit resyncs still send a complete snapshot.
|
||||
|
||||
Clients that omit `chunk_stream_v1` retain protocol 1's original bounds, `[centerX-32, centerY-8, centerZ-32]…[centerX+31, centerY+31, centerZ+31]`, and receive replacement `snapshot` messages on movement at most once every 2 seconds. Unknown feature names are ignored.
|
||||
|
||||
Snapshots do not include the full string registry. `materials` is limited to 256 entries and 128 KiB; chunk updates include only definitions used by entering blocks. The client requests any remaining definitions sequentially through `/api/catalog?ids=1,2,...&limit=128`. This allows players to join worlds with large palettes without overflowing the outbound queue.
|
||||
|
||||
The client sends `input` with `seq,yaw,pitch,forward,strafe,jump,sprint,sneak,fly_toggle`; `break` with `pos`; `place` with `pos,block` or `state`; and `switch_world`, `resync`, `respawn`, `start_match`, `chat`, and `ping`. The three added movement flags default to false for older clients. A `state` response contains authoritative positions, `tick`, the processed input `ack`, and match state. Public players also contain velocity in blocks per tick, pose, height, eye height, sprinting and flying. Blocks arrive in `blocks` messages with a new revision. If a revision is missing, the client requests a snapshot. Subscription and snapshot creation are serialized with edits on the same thread, so an edit made between those steps cannot be lost.
|
||||
|
||||
Clients negotiating `movement_prediction_v1` send one movement command every 50 ms. At most 32 commands are queued, and one is consumed per server tick; packet bursts never create additional simulation ticks. Duplicate sequence numbers are ignored. When the queue is empty the last controls remain held, with flight toggles consumed once. Stale controls clear after one second. `welcome`, `snapshot` and `state` include `motion:{body,ack,tick,epoch,settings}`. The body is the complete shared solver state, including velocity, pose, contact flags and jump cooldown. Settings are chosen by the server, including creative flight permission and arena countdown freeze. The client restores this state and replays unacknowledged commands through the shipped `/physics.wasm` module.
|
||||
|
||||
`input_reset` clears queued and held controls, acknowledges discarded commands and advances the motion epoch. Respawn and world/arena teleports also advance the epoch so prediction discards obsolete input. `look` with `yaw,pitch` updates the action ray without consuming a movement sequence; the browser uses it immediately before a block action. Final client positions, velocities and flight permission are never accepted as authority. Clients without the movement feature keep the earlier latest-input behavior and authoritative state updates.
|
||||
|
||||
Control edits retain the core contract: an expected revision, a unique `operation_id`, a durable acknowledgment, replay of the same request without a second write, and rejection on conflict. A build plan covers at most 32,768 cells and is retained for 5 minutes, with at most 16 plans stored; preview does not change the world. Plans are ephemeral and disappear after a restart; accepted edits remain on disk. `camera.capture` returns a PNG of the server's isometric projection and the exact revision; it is not a frame from the player's WebGL camera.
|
||||
|
||||
@@ -67,7 +79,7 @@ The core idempotency journal covers block edits, undo/reset, and build commits.
|
||||
## Limits and observability
|
||||
|
||||
- 32 WebSocket sessions, including those waiting to join; 16 concurrently served HTTP requests; a 64-command queue.
|
||||
- WebSocket input messages up to 64 KiB, with at most 80 messages/s per connection; block actions no more often than every 110 ms, chat every 700 ms, explicit resync every 500 ms, world switches every second, and automatic snapshots on section changes every 2 seconds.
|
||||
- WebSocket input messages up to 64 KiB, with at most 80 messages/s per connection; block actions no more often than every 110 ms, chat every 700 ms, explicit resync every 500 ms, and world switches every second. Negotiated chunk updates run at most every 250 ms on section changes; legacy automatic snapshots retain their 2-second interval. Movement streaming uses a separate timer and does not delay explicit resyncs or world switches.
|
||||
- Control API JSON up to 2 MiB; standard edits up to 32,768 cells; reads up to 262,144 cells and 6 MiB of response data. Exceeding the byte limit requires a smaller region.
|
||||
- Each client's outbound queue holds up to 16 messages and 8 MiB. A slow connection is closed if its queue overflows or sending times out. The aggregate queue bound depends on the number of clients; these bytes are separate from the section cache.
|
||||
- Server metadata up to 8 MiB, at most 4096 entities, and up to 16 KiB of properties per entity. Gameplay snapshots contain compact representations without arbitrary property JSON; full properties are available through `entity.list` with offset and limit (default 64, maximum 128).
|
||||
|
||||
Reference in New Issue
Block a user