Repository navigation
feat(game)!: zones as celld objects, Mob Pit room grid - #287
Draft
patrickleet wants to merge 16 commits into
Draft
patrickleet wants to merge 16 commits into
patrickleet wants to merge 16 commits into
Conversation
One Mob Pit room runs as a celld Durable Object (wasm32 Rust): the existing ZoneHost, wire protocol and HS256 tickets, a setTimeout tick loop while occupied, a hibernation snapshot in object storage, and the celld node / isolate / activation replicated as a global `host` entity. run.sh starts a 3-node production-mode fleet on one RustFS bucket; tools/ hold the headless test and the measurement scripts. Framework changes the spike needed: a `ticket` feature (admission tickets without the axum server) and wasm32-safe tick timing in ZoneHost (no `Instant` on wasm32-unknown-unknown). Spike only: not wired into the demos. [[tasks/gr-p42-zones-as-celld-objects-spike]]
[[tasks/gr-p42-zones-as-celld-objects-spike]]
distributed_game's `celld-zone` feature hosts any Zone as a celld Durable Object, one object per zone id: celld places, forwards, evicts, rebalances idle objects and recovers crashed ones; game code never names a server. The spike's PitRoom glue becomes framework code: - ZoneObject<Z: CelldZone> + celld_zone_object!: hibernatable WebSocket joins (ticket, single-use transfer ticket, reconnect token), a self-correcting deadline tick loop while occupied, snapshots in object storage, dormant -> hibernated by object alarm, restore on wake, 1012 for sockets of an earlier activation, GET .../info debug JSON. - Facts object to object: CelldObjectHost under CommandFactSink, with a durable outbox in object storage (FactQueue journal) so facts reach cells exactly once across crashes. - world::zone_activity: the zone reports active / dormant / hibernated as facts into its ZoneActivity cell; zone_status projects them into zone_activities (ZoneStatus::Dormant, of_activity). - Debug-only node lookup via operator listeners (CELLD_ZONE_NODES), off by default. Breaking: CommandFactSink is portable (feature `cell`) over the new FactHost trait, which replaces FactRouting and the CommandHost bound. The JS GameConnection rejoins with a fresh ticket on close 1012 (GAME_CLOSE_RESTARTED). Tests: celld_zone on celld dev and on a 3-node RustFS fleet (owner killed mid-flight: 1012, recovery elsewhere, 200 facts exactly once), zone_status activity projection, plan unit tests, wasm32 domain check. [[tasks/gr-p43-celld-zone-host]]
Bots append `{path}/{zone}` (default `/zones`), so they can join zones a
celld worker routes elsewhere (`/rooms/{room}`). A celld zone object's
`GET …/info` debug JSON allows any origin, so pages served elsewhere can
show which node and isolate run the zone.
[[tasks/gr-p43-celld-zone-host]]
Mob Pit becomes a near-infinite seeded grid of rooms (`pit.<x>.<y>`),
each one a celld Durable Object hosted by distributed_game's celld zone
host. celld decides where rooms run; nothing in the game names a node.
- game_sim::mobpit: `pit_room` (#[game_pure], biome from the Endless
Grid's noise, danger, 6-12 mobs), doorway and arrival rules,
`PIT_WORLD`; the page draws rooms with the same Rust as WASM.
- mobpit_rooms (new, wasm32-portable): the room zone (doorway handoffs
with carried position, hibernation snapshot of the mobs and the room's
kills, profile loaded from the Crawler cell after joining) and its
CelldZone hooks (kill facts to crawler cells, lifecycle facts to
presence cells, object to object).
- cells worker: PitRoom objects behind `/rooms/{room}`, ZoneActivity
cells feeding the zone status read model through the outbox Queue.
- game-demos mobpit is the services side only (dev room tickets, relay,
crawler directory, room status, boards fed from the directory with
idempotent max updates). Removed: the native zone, its fact sink, the
slow-storage knob and the in-memory presence mirror.
- Vite page: rooms through `/rooms` (proxied to celld), doorways, biome
floor, live minimap of room status, restore toasts, debug overlay
with the celld node/isolate per room. run.sh starts celld dev itself;
`run.sh mobpit --fleet [N]` runs N production-mode nodes on one
RustFS bucket (docker compose) behind a round-robin dev balancer.
Tests: mobpit_rooms reference-host tests; mobpit_smoke on celld (bots
in a room, dormant -> hibernated -> restored, celld kill -9 mid-fight,
presence cells); mobpit + leaderboard headless; pit_room WASM = native.
[[tasks/gr-p41-mobpit-rooms-zones]]
The SvelteKit Mob Pit joins the entrance room on celld and follows doorways from room to room; tickets admit the entrance or a room being rejoined (after a 1012 restart), never chosen by a node. - mobpit-service projects the rooms' zone-activity events into `zone_activities` next to `player_stats`, readable over GraphQL @LiVe. - The page: a live minimap (Rooms.graphql @load @LiVe: active, dormant, hibernated, never ran) with restore and sleep toasts, a debug overlay of the room object's node, isolate and activation, doorways drawn on the walls; the room entry points rotate per connection (celld forwards every socket to the room's node). - Playwright: two players on a 3-node production-mode celld fleet (RustFS via docker compose, tests/helpers/fleet.mjs): kill, live read model, doorways into a room on another node, the left room asleep on the minimap, restored on return, another kill settled live. [[tasks/gr-p41-mobpit-rooms-zones]]
The Mob Pit, Leaderboard and SvelteKit docs describe rooms as celld objects (doorways, dormant/hibernated/restored, the minimap, the debug overlay, `run.sh mobpit --fleet`) and the services that remain native. The spike crate (examples/game/zone-do-spike) is gone: its glue is the framework's celld zone host and Mob Pit's rooms; its measurements stay in the spike task. CI's game-demos smoke requires the fleet for the Playwright test. [[tasks/gr-p41-mobpit-rooms-zones]] [[tasks/gr-p43-celld-zone-host]]
When Docker runs the RustFS container but its port never becomes reachable, the Playwright fleet helper now stops the container and falls back to `celld dev` (or fails with MOBPIT_FLEET_REQUIRED=1, as CI sets), instead of failing after a minute. [[tasks/gr-p41-mobpit-rooms-zones]]
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
GameClient used to hold the controlled entity still while a handoff was in flight and drop the inputs sampled meanwhile, so a border crossing showed a hold, then a hop. With prediction.handoffFrame (the predicted fields re-expressed in the target zone's frame) the client keeps predicting in the target's frame, sends the transfer's inputs to the target once it welcomes the player, and eases from what was drawn into the target's state. Predictor.bind keeps (and replays) inputs recorded while no entity or the same entity was bound, filtered by the delta's ack, instead of dropping them; new step() and easeFrom(). [[tasks/gr-p41-mobpit-rooms-zones]]
Rooms sit at fixed world places (room (x, y) at (x, y) x 40 m) and a doorway is a passage: pit_step walks through the gap past the wall line, the room hands the player over 0.5 m past it (hysteresis) and the next room admits it at the same world point with its velocity (carried MoveState in the target frame) instead of 2 m inside the opposite wall. Player x/y widen to +-22 m on the wire. Both pages draw the world: the Vite page builds the 5x5 rooms around yours from pitRoom (biome floor, walls with doorways, pillars) under a floating origin; the SvelteKit canvas follows your world position over the 3x3 rooms around. Both predict through the handoff (handoffFrame). Tests: rooms test checks the arrival world point; headless and Playwright walks assert the own world position never moves more than one tick across a doorway. [[tasks/gr-p41-mobpit-rooms-zones]]
A zone that refuses a Hello (expired reconnect token, bad ticket) sends Error and should close the socket. celld room objects send the Error but the close never reaches the client, so after a celld restart a connected page tried its void token and then hung in 'reconnecting' forever. The client now ends the attempt itself on a pre-Welcome Error: a resume falls back to a fresh ticket, a refused first connect rejects. Refs [[tasks/gr-p45-same-player-after-restarts]]
A page reload is a fresh ticket for a slot its principal still holds. The default Zone::on_rejoin (leave, then join) respawned the player, so every reload lost the body even inside the reconnect grace. Arena and Endless Grid now keep it and re-greet the new page; the on_rejoin docs say why a game wants that. Refs [[tasks/gr-p45-same-player-after-restarts]]
run.sh gave Arena, Grid, Ripple, Gallery and Freeze a random ticket secret per run, so a returning browser's guest token no longer verified and it became a new player. Every demo now signs with a secret persisted in .data/<demo> (SvelteKit also keeps its Auth.js secret), and the cells worker is rebuilt when the room or sim crates change. Refs [[tasks/gr-p45-same-player-after-restarts]]
A reload, a new tab, closing the browser, or a services or celld restart put a returning Mob Pit player in a new body at the entrance spawn point: - the rooms used the default rejoin (leave + join), discarding the body; - nothing chose the player's last room (Vite joined ?zone= or pit.0.0, the SvelteKit ticket handler the entrance); - a room forgot a player's place when its slot ended; - the dev guest token lived in sessionStorage (one guest per tab). Rooms now keep the body on rejoin and park the place and HP of a player whose slot ends (and of connected players in every snapshot) by verified principal, so a fresh join resumes there after the room slept or celld restarted. Page loads start in the last room from the presence cell: POST /dev/mobpit/start (guest token verified) for Vite, the ticket handler's zone() for SvelteKit. The guest token moves to localStorage (?player=<label> for several guests per browser). Tests: room unit tests, a headless test per restart kind, and a reload plus a fresh sign-in in the SvelteKit Playwright test. Refs [[tasks/gr-p45-same-player-after-restarts]]
10 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #285. Runs live game zones as celld Durable Objects and turns Mob Pit into a grid of rooms joined by doorways (GitKB tasks gr-p43, gr-p41; spike gr-p42).
Framework:
celld-zonefeature indistributed_gameZoneruns as one celld object per zone id: implementCelldZone, declare the class withcelld_zone_object!, route withforward_to_zone. celld decides which node runs it.ZONE_HIBERNATE_AFTER_MSit hibernates (dropped from memory, restored on next join). Driven by an object alarm, so it survives eviction.world::zone_activitycell +zone_statusgain aDormantstatus.GET /rooms/{room}/infodebug JSON; node name is debug-only (CELLD_ZONE_NODES/CELLD_ZONE_NODE), nothing routes by it.Mob Pit rooms
pit.<x>.<y>; seeded biome, danger and 6–12 mobs fromgame_sim::mobpit::pit_room(same Rust in the browser as WASM); a doorway in the middle of each wall.mobpit_rooms(wasm32, in the wasm32 check). Snapshot keeps mobs and the room kill count.@live, sleep/restore toasts, debug overlay (O) showing node/isolate/activation. Vite page polls/dev/mobpit/rooms.run.sh mobpit [--fleet [N]]starts single-nodecelld devor a local multi-node fleet with RustFS.game-demos mobpitis services only; the native Mob Pit zone, its fact sink and the spike crate are removed.Breaking (v5 unreleased):
CommandFactSinkis over a newFactHosttrait (replacesFactRouting);BotConfiggainspath.Verified
celld devand a 3-node fleet, including SIGKILL of the room's node with 200 facts in flight → recovered on another node, exactly 200 hits. Playwright two-player SvelteKit test across nodes passed.run.sh smokeOK (single node);cargo test --workspace --all-features --all-targets130 suites ok, one known flaky upstream SQLite bus test (passed 3/3 on rerun); JS 491 pass; wasm32 check ok.Open
run.sh mobpit --fleetnot yet run end to end via the bash path (local Docker stopped publishing ports); CI setsMOBPIT_FLEET_REQUIRED=1.