M2.2: player bodies, name tags, shared island and checked movement

- Joining loads the server's island (seed, spawn, clock); the server
  generates the same terrain from its seed and keeps a world clock that
  pauses when empty
- Other players are procedural low-poly characters in their chosen colours
  with name tags (hidden by walls, fading with distance), walk/run/swim
  animation and held items
- Movement: local and instant, sent at 20 Hz stamped with the sender's
  clock; the server refuses impossible moves (speed, rising, leaving the
  island, underground) and sends the player back; flagged respawns allowed
- Avatars drawn 100 ms behind and interpolated, with brief extrapolation
- Online: no pausing, player list in the Esc menu, Disconnect back to the
  title (with the reason); animals off and world changes local-only until
  M2.3-M2.5, shown on the HUD
- test_net_world: real server + walking and cheating bots at 100 ms +/-
  30 ms simulated latency (27 checks)
This commit is contained in:
Paul Bailey
2026-11-04 15:08:14 +00:00
parent f62f71658f
commit d0f473e57b
14 changed files with 1093 additions and 29 deletions

View File

@@ -147,12 +147,39 @@ transfer...) becomes a client-to-server RPC. The server runs the same
function it runs today, with the server's copy of the player, then sends
back the results. The rules don't move; only who calls them.
**Movement.** The client moves its own player immediately and sends inputs
(30 Hz). The server simulates the same inputs and sends back authoritative
positions. The client corrects only if it's off by more than a small
tolerance, which keeps things smooth on Tailscale and home links. Basic
anti-cheat: the server rejects moves faster than sprint, or through walls
and the world boundary.
**Movement** *(built in M2.2: `net.gd`, `net_sync.gd`, `avatar.gd`)*. Your
own player moves locally with no delay, and the game sends its position,
facing, animation and held item to the server 20 times a second, each
stamped with the sender's own clock. The server checks every move against
the last accepted one:
- no faster than 9.5 m/s across the ground (sprint is 8) plus 1 m of slack;
- no rising faster than 9 m/s;
- not outside the island (a 3 m margin past the wall);
- not underground.
A refused move sends the player back to their last good position.
Respawning is a flagged teleport, allowed if it lands within 25 m of the
island spawn. Normal play is never corrected, so there's no rubber-banding.
This is the Valheim model, and simpler than replaying inputs on the server.
Through-wall checks come with the shared world (M2.3), since the server
doesn't know about buildings yet.
The server sends everyone's positions 20 times a second. Each one is stamped
with when the player moved (their clock plus a smoothed per-player offset),
not when it was sent. Stamping by send time bunched moves into some
snapshots and left others empty, so avatars surged. Avatars are drawn
100 ms behind and interpolated, carrying on along their last velocity for up
to 0.25 s if data stops. The server also sends its seed, spawn point and
clock (every 10 s; the clock pauses when the server is empty), so every
player has the same island, sky and weather.
Measured in `tests/test_net_world.gd` at 100 ms ± 30 ms simulated latency:
a 4 m/s walk is seen at a median of 4.2 m/s, top 5.0, with no freezes, and
it ends within a centimetre of where the walker stopped.
Until later phases, while online: animals are off (M2.5), and building,
gathering and other world changes are local only and aren't saved (M2.3,
M2.4). The HUD says so. Respawning ignores beds.
**Interest.** On a 320 m worldlet with 8 players, the server sends every
entity to everyone at first. Distance-based interest is a later
@@ -209,7 +236,7 @@ player" or "this player":
|---|---|---|
| **M2.0** | worldlet W0 (terrain from data, no visible change), recommended first | seed 1337 generates the same height grid as today; all suites pass |
| **M2.1** *(done)* | network skeleton: `--server`, ENet on 24242, Join screen (address, name), handshake with identity and challenge, name rules, player list | two clients and a headless server on one Mac connect, see the player list, and disconnect cleanly |
| **M2.2** | player bodies and name tags; predicted movement, interpolation for others | two players walk around together at 100 ms simulated latency without visible rubber-banding |
| **M2.2** *(done)* | player bodies and name tags; predicted movement, interpolation for others | two players walk around together at 100 ms simulated latency without visible rubber-banding |
| **M2.3** | world state replicated: resource nodes, pickups, pieces, chests, fires, doors, clock, weather | one chops a tree and the other sees it fall; one builds a wall and the other walks into it |
| **M2.4** | intents as RPCs; per-player inventory, stats, diet, death and graves; per-player saves and migration | two players craft, eat, die and loot their own graves; an old single-player save loads |
| **M2.5** | wildlife and wolves server-side, projectiles, fishing, sleeping with the rule | a pack hunts two players at once; arrows hit on both screens |

View File

@@ -45,7 +45,7 @@ or port forwarding, player identity and names, server rules for co-op/PvP, and
phases M2.0-M2.7).
- [ ] Refactor state to server-authoritative (single-player becomes loopback server)
- [x] Headless dedicated server, joining, identity handshake, names, player list *(M2.1)*
- [ ] ENet transport, client prediction for local movement, interpolation for others
- [x] ENet transport, player bodies and name tags, local movement checked by the server, interpolation for others *(M2.2)*
- [ ] Replicate resource nodes, inventory, building, health
- [ ] Persistence: world save/load on the server
- **Done when:** a friend joins your self-hosted server and you build together.

View File

@@ -38,6 +38,8 @@ godot --headless --path . --script res://tests/test_survival_core.gd
godot --headless --path . --script res://tests/test_save_load.gd
# Multiplayer lobby: a real headless server on UDP 24343 + scripted bot players
godot --headless --path . --script res://tests/test_net_lobby.gd
# Multiplayer world: avatars, smooth movement at 100 ms latency, cheat checks (UDP 24345)
godot --headless --path . --script res://tests/test_net_world.gd
# Survival stats: hunger, warmth, fire fuel, cooking, foraging, death + grave
godot --headless --path . --script res://tests/test_survival_stats.gd
```