Records the live production state (Linode IP, paths, redeploy command,
firewall + fail2ban + SSH hardening, TLS via Caddy, DNS via Namecheap
Advanced DNS, rollback steps, troubleshooting checklist) so a fresh
session can pick this up without re-deriving any of it.
Reshape the engine into a functional-core / imperative-shell split.
The 2500-line state.rs blob is now 1700 lines of mostly GPU plumbing
plus a thin tick() shell that composes pure transformations from the
new sim/ and net/ modules.
src/sim/ (no GPU, no winit, no thread-locals — all pure):
body.rs PlayerBody value type + take_damage / respawned_at
collision.rs AABB primitives, sweep_axis returning (Vec3, bool)
instead of mutating &mut Vec3
edit.rs block_from_u8, apply_edit, chunks_for_edit
event.rs SimEvent enum (Landed, VoidDeath, BlockEdited)
input.rs TouchBridge data type + merge_held
physics.rs step_movement(world, body, MoveInput) -> MoveOutcome
— the central morphism: one tick of player movement
+ collision + landing detection + void check, returns
a new body value and a list of events
spawn.rs find_safe_spawn, fall_damage
src/net/:
parse_inbox(Vec<String>) -> Vec<NetEvent>
Malformed lines drop silently; the shell folds typed events into
the world / remote-player map.
src/state.rs:
- App now holds a single PlayerBody value instead of scattered
velocity / on_ground / hp / alive / max_y_since_ground fields.
- tick() is a pipeline: collect input → merge_held → step_movement
→ fold sim events → block interaction → render_frame.
- drain_net_inbox() parses to events first, then applies.
- render_frame() extracted so the paused and active branches share
the upload + cull + draw path.
Tests: 45 passing (up from 33). New coverage for the physics step
itself: airborne gravity, jump-only-when-grounded, long-fall-emits-
Landed, void floor, dead-body-does-not-move. Net parsing tests for
malformed lines and round-trips. Body tests for damage / respawn.
Build: cargo test (45/45), cargo build --target wasm32-unknown-unknown
--lib --release, and the axum server all green.
Multi-stage Dockerfile compiles wasm client + axum server in one Rust
builder and copies into a debian:bookworm-slim runtime (non-root uid).
docker-compose.yml binds localhost:8080 by default; docker-compose.prod.yml
replaces ports with a Caddy reverse proxy on host 80/443 that talks to
the voxel container over the internal network. Caddy auto-issues Let's
Encrypt certs.
DEPLOY.md covers the three deployment modes (local-only, VPS with
Cloudflare or Caddy, Cloudflare Tunnel from a workstation).
- Greedy meshing now bakes per-vertex AO with 4-corner sampling and an
anisotropic-diagonal split when corner AO disagrees.
- WGSL: extracted sky_dome() for hemisphere ambient sampling so vertical
faces match the sun-side sky tint at day; ambient_strength mixed by
day strength instead of a flat constant.
- Step-1 post pipeline: render scene into an offscreen color texture,
pass-through to the surface. Foundation for FXAA/shafts that will
follow.
- Input bug: merge_held() now recomputes per tick from sticky keyboard +
live touch bridge, so releasing the joystick actually stops the
player (previous OR-into-self bug ate playtests).
- Touch UI hit-zones reordered (menu/hotbar above the joystick z-index);
hotbar widened to 10 slots with tap-to-select on mobile.
- find_safe_spawn anchors on natural_surface_y so spawn is deterministic
from noise — towers built at spawn no longer climb the spawn point.
- move_axis is sub-stepped (0.45-block max) so high-velocity falls can't
teleport the player inside terrain.