Chunk generation and sending no longer block the read loop. The read
loop pushes a non-blocking recenter request and stays free to handle
movement, chat, and keep-alive acks immediately; a per-connection
streamer goroutine generates chunks in a worker pool and sends them
serially under the write mutex.
- network/streamer.go: per-connection streamer. spiralOrder emits
chunks centre-outward; parallelSend fans Cache.Frame across a worker
pool (Cache.Frame is already goroutine-safe), parallelGenerate warms
a one-ring predictive border so movement finds ready chunks; the
loaded-set is owned solely by the streamer.
- network/play.go: beginPlay launches the streamer and pushes an
initial recenter instead of the old blocking streamAround;
onPlayerMove now just calls requestRecenter (non-blocking).
- network/handler.go: ctx (connection lifetime) + streamer field; the
streamer stops when the read loop ends (cancel on serve exit).
- network/configuration.go: client view_distance is saved (clamped
2..16) and drives the streamer radius.
- Tests: spiral order (centre-first, ring structure) and the
non-blocking recenter guarantee the read loop relies on.