Commit Graph

9 Commits

Author SHA1 Message Date
daddy fd88cef32f Wire client chunk streaming + dedicated-server content load to the backend
Implements the client-side half of docs/architecture/world_persistence_impl.md
(the backend API in backend/app/routers/{world_content,internal}.py and
game_servers.py's --api-url/--server-token/_await_ready were already merged):

- scripts/http_chunk_source.gd: new HttpChunkSource adapter satisfying
  ChunkStreamManager.set_server_rpc()'s seam. Fetches chunks over HTTP from
  the public world_content API (JWT via ApiClient), with ETag/If-None-Match
  (304) support backed by a user://chunk_cache/<checksum> cache, and a
  bounds-query list_chunks() helper (§8.1's chunk index). A 404 (unwritten
  coordinate) renders empty instead of erroring, per §8.1.

- scripts/server.gd: startup now parses --api-url/--server-token, and
  before binding the ENet port fetches the world's manifest + the
  spawn-neighbourhood chunks from /api/internal (§7.2/§7.3/§8.3), decodes
  them, and builds real StaticBody3D collision from solid tiles + non-
  walkable objects. Falls back to test_level.tscn if the backend is
  unreachable, the world has no rooms, or the room has zero chunks -- never
  hangs or crashes. Writes "ready"/"content" (plus diagnostic
  chunks_loaded/collision_shapes counts) into the status file that
  game_servers.py's _await_ready() already reads.

- scripts/api_client.gd: exposes a `base_url` getter so other client code
  building its own requests (HttpChunkSource; server.gd doesn't use this
  autoload at all, since its auth model is the per-spawn server token, not
  a player JWT) can target the same backend.

Also fixes three real, previously-dormant bugs this work's first real
exercise of these code paths uncovered (nothing before this called
encode_binary() or fed real data through set_server_rpc()):

- chunk_data_decoder.gd: `_build_metadata_section` never wrapped its
  output with the section id/length header every other section builder
  uses, so `encode_binary()` produced corrupt payloads the decoder read as
  a single garbage-length section ("truncated section body").
- chunk_stream_manager.gd: `_parse_coord_from_key` assigned a
  PackedStringArray (String.split()'s real return type) to an
  Array[String]-typed variable -- a hard crash.
- chunk_stream_manager.gd: `_instantiate_chunk_scene_with_data` read a
  nonexistent `data.format_version` field; ChunkPayload's field is
  `version`.

Adds scripts/smoke_test_http_chunk_source.gd, a real end-to-end smoke test
(register, create world, PUT a real chunk, stream it back through
ChunkStreamManager + HttpChunkSource, plus the unwritten-coordinate and
304-refetch paths).

Verified against a real backend + Postgres:
- Seeded real chunk content, joined through the real
  GameServerSupervisor spawn path: status file reports
  content=loaded, chunks_loaded=1, collision_shapes=33.
- A room with zero chunks and an unreachable backend (closed port) both
  fall back to test_level.tscn cleanly (content=fallback, ready=true, no
  hang/crash).
- Full existing Godot smoke suite (16 suites, including
  smoke_test_enter_world.gd and smoke_test_network_client.gd) passes with
  no regressions.
- Backend pytest (132/132) untouched and still green -- no backend/
  changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2FESCaMXeJpC9gSctiEeD
2026-09-14 22:54:56 +00:00
daddy 3639fb3132 Fix multiplayer state: stacked players, ghosts after rejoin; add a build stamp
checkpoint / checkpoint (push) Successful in 1m40s
- Clients now hard-snap to the server's spawn position on the first
  snapshot. player.gd starts everyone on the fixed PlayerSpawn marker but
  the server spreads spawns out -- so one client saw players ~1m apart
  (correct) while the other saw them stacked (its local player still on the
  marker, never corrected because the ~1m drift was under the old 1.5m
  threshold). Threshold also tightened to 0.6m.
- game.gd._exit_tree() closes the ENet connection cleanly when you leave a
  world, so the server fires peer_disconnected and removes your player.
  Before, leaving just changed scene and the server never knew -- everyone
  else kept seeing a frozen "ghost".
- server.gd: once-a-second sweep of players whose peer is no longer
  connected, as a backstop for a missed disconnect.
- Build stamp: checkpoint_ops bakes "<PC> <commit>" into
  application/config/version at export; the menu and in-world HUD show
  "build PC3 <hash>" so playtest reports aren't ambiguous about which
  build is running.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-06 00:39:30 +00:00
daddy da4e1420ee Fix: rejoining stacked players on top of each other (one fell through)
checkpoint / checkpoint (push) Successful in 1m40s
All players spawned at the exact same PlayerSpawn point and were on the
same collision layer, so two overlapping CharacterBody3Ds triggered a
physics depenetration shove that could launch one through the 1-unit-thick
floor.

- server.gd: golden-angle spawn spread -- each join lands ~1m+ from the
  others, growing outward, never exactly repeating.
- server_player.tscn + player.tscn: players on collision layer 2, mask 1
  (world only) -- players no longer collide with each other at all
  (fine for a social game; also removes the shove).
- player_spawner.gd + player.gd: hard safety net -- if y < -10 (any
  tunnel-through), snap back to the spawn instead of falling forever.

Verified: 3 server players spawn 1.15m+ apart, none fall, all rest on the
floor. 2p multiplayer + network_client smokes green.

Client-side -- rebuild the exe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-06 00:00:27 +00:00
daddy 682f1f89f7 PC3 Phase 2.1: server-authoritative sim + prediction/reconciliation with replay
Made the multiplayer core actually work (it had never run a real sim):

- project.godot: physics pinned to 30 Hz -- the shared sim/snapshot/prediction rate.
- server.gd: loads the world's level (real ground + collision), spawns a
  server Player per peer at the level's PlayerSpawn, broadcasts a full
  snapshot every physics frame with each peer's state AND last_seq (the last
  input seq applied for that peer). Accepts `--port/--world/--status-file`
  args (status file = player count for a future idle reaper); defaults keep
  `godot res://scenes/server.tscn` working standalone.
- player_spawner.gd (server Player): rewrote -- drains its whole input queue
  each physics frame in seq order, integrates gravity/walk/jump with the
  shared constants, move_and_slide(), tracks last_applied_seq. No more no-op
  process_server_input().
- network_client.gd: rewrote reconciliation -- drops inputs the server has
  acked (last_seq), re-simulates the still-in-flight ones from the
  authoritative state, snaps only on real drift. Exposes get_remote_states()
  (peer_id -> latest snapshot state) for the game to render other players.
  predict_and_send() now also sends body yaw.
- proxies trimmed to the RPC set the rewrite actually uses.
- scripts/mp_test_client.gd + run_multiplayer_2p_test.sh: two real client
  processes vs one real server -- client A sees client B and its own
  movement round-trips. PASS.
- run_checkpoint_tests.py: also run the 2p shell test.

smoke_test_network_client 4/4, player 15/15, avatar_in_world 10/10, all green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 22:00:18 +00:00
claude-checkpoint-tooling 7be50e3313 Fix real scene collision + a real regression from removing the Server autoload
Two genuine bugs, found by reproducing rather than trusting the branch's
own state:

1. scenes/player.tscn collided with PC2's client-side player scene at the
   exact same path (server-authoritative PlayerNode vs. the local
   single-player character) -- whichever merged last would silently
   overwrite the other. The branch's own last commit had literally
   committed unresolved <<<<<<< conflict markers into the file instead of
   resolving them. Renamed the server's version to
   scenes/server_player.tscn, restored scenes/player.tscn to match main's
   real client scene exactly, repointed scripts/server.gd's preload.

2. scenes/server.tscn's root Node had no script attached at all -- it only
   ever worked before because project.godot had `Server` registered as a
   project-wide autoload (the exact wholesale-overwrite this task was
   already asked to fix). Once that autoload was correctly removed,
   nothing attached server.gd to the scene tree anymore and the dedicated
   server silently did nothing. Attached scripts/server.gd directly to
   scenes/server.tscn's root node instead.

Verified: godot --headless --import/--quit both clean, full 10-suite
client smoke test all passing, and the dedicated server actually listens
on UDP port 7777 when launched explicitly (`godot --path . scenes/server.tscn`)
-- confirmed with `ss -ulnp`, not just trusting the startup print (first
checked with `ss -tlnp`, which only shows TCP and falsely suggested it
wasn't listening at all; ENet is UDP).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
2026-08-21 00:59:24 +00:00
architect-bot 6018396be2 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:26:42 +00:00
architect-bot ee6513b8ad Recovered orphaned WIP before switching to a new task 2026-08-17 18:16:22 +00:00
architect-bot 37a311b0c5 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:15:38 +00:00
agent-bot 7cd41dc920 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00