Repository navigation
feat(game)!: Mob Pit world, fold demos in - #288
Draft
patrickleet wants to merge 23 commits into
Draft
patrickleet wants to merge 23 commits into
patrickleet wants to merge 23 commits into
Conversation
The development network simulation (latency, jitter, loss as a
retransmit delay) moves to a portable `distributed_game::netsim`
module (`NetSim`, an order-preserving `DelayLine` over millisecond
timestamps, `NetSimJson` for dev routes); `net` re-exports `NetSim`.
Zones hosted as celld objects apply it per socket and direction:
outbound frames (and closes) and inbound inputs and pings go through
delay lines drained by `setTimeout` tasks. With ZONE_DEV_SETTINGS=1 a
zone reads the setting from the worker's dev settings object
(`celld_zone::ZoneDevSettings`, `zone-dev/netsim`) on wake and every
second while it ticks; `/rooms/{zone}/info` reports it.
Refs [[tasks/gr-p44-mobpit-world]]
Mob Pit gets the Arena's network panel on its celld rooms: the HUD's latency / jitter / loss sliders write the cells worker's dev settings (`/dev/netsim` on the services, a dev-only SvelteKit route) and every room delays its sockets; P toggles prediction, G shows the server ghost (the room's authoritative copy), the readout counts pending inputs, acks and corrections, and the dashed ring is the interest radius (24 m, now in Hello): mobs and players outside are not replicated. The Arena demo is deleted (server module, smoke test, page, generated client, headless test, run.sh entry, README rows). The Freeze keeps the Arena capsule view and bot encoder; PIT_WORLD carries the floor constants Mob Pit drew from ARENA_FLOOR. Tests: new mobpit-network-headless (exact WASM prediction against the room, NetSim raising RTT through celld, interest culling); SvelteKit Playwright checks the latency slider and the ghost gap. Refs [[tasks/gr-p44-mobpit-world]]
|
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 |
On zones hosted as celld objects an observer is a passive watcher: it never counts as occupancy (a zone with only observers stops ticking, goes dormant and hibernates, closing their sockets) and never wakes a sleeping zone — its Hello is refused with the new error code ASLEEP (10). Only a player's Hello wakes a zone (the WebSocket is accepted before waking); a resume to a cold zone is RECONNECT_FAILED without waking. A watcher joining a dormant zone is greeted with its current state (`ZoneHost::patch_now`). SDK: `watchZones(client, around)` keeps the zones around the authority observed across handoffs, lets go of closed watchers and retries them with back-off; `GameErrorCode.ASLEEP`. The celld_zone integration tests gain `watchers` and run one celld at a time. Refs [[tasks/gr-p44-mobpit-world]]
Mob Pit watches the 8 rooms around the player's own (Vite and SvelteKit, `watchZones`): their mobs are drawn where they are, a sleeping room is not woken and shows nothing, and a watched room still goes dormant and hibernates when its last player leaves. The room gives a watcher the whole room; the HUD counts rooms watched and asleep, the minimap outlines the watched ones; the leaderboard page's entrance watcher retries while the entrance sleeps. The SvelteKit ticket handler allows the observer tickets this needs. The Endless Grid demo is deleted (zone, dev map routes, page, generated client, tests, run.sh entry, README). `game_sim::grid` keeps only the seeded noise, biomes and props Mob Pit's rooms use. Tests: mobpit-neighbours-headless; the SvelteKit test checks a player sees the mobs next door while the watched entrance still hibernates. Refs [[tasks/gr-p44-mobpit-world]]
A zone hosted as a celld object can declare `refresh_every` and `refresh(env, zone_id, round)`: async work off the tick (e.g. reading the cells of the tiers it belongs to) that runs on every wake and at that interval while the zone ticks; the message it returns is delivered into the zone. `round` counts calls per activation so slower tiers can be read less often. Refs [[tasks/gr-p44-mobpit-world]]
Rooms group into hoods (3 × 3) and boroughs (3 × 3 hoods) under one city. Each tier is an aggregate cell on celld (new crate mobpit_tiers): every hood has a seeded boss room; a boss kill is a zone fact into the hood (only from that room, only into its hood) that raises the hood's tier and schedules the boss's return (ctx.schedule). The worker's Queue consumer runs the saga that ripples hood events into the borough (bosses down, weather) and borough events into the city (banner): idempotent commands keyed by event id, stale steps ignored by stream sequence. Rooms read their tiers off the tick (CelldZone::refresh) and tell their players; a hood's tier makes its rooms tougher, a fallen boss stays down until its hood brings it back. Vite and SvelteKit show the borough's weather (Boss Ripple's fog, clear colour, light) with fog and darkness thickening toward boss rooms (boss_distance from the shared room generator), boss rooms and hood / borough borders on the minimap (/dev/mobpit/tiers), and the tiers in the HUD. Boss Ripple is deleted with the native multi-host plumbing only it used (cluster.rs, test support, run.sh cluster runner, the worker's zone lease and zone state cells). Tests: mobpit_tiers (hood → borough → city on the reference host, respawn schedule, stale steps), rooms (a party fells a boss, the fact, the boss stays down), mobpit-bosses-headless (the saga end to end on celld), SvelteKit weather and darkness. Refs [[tasks/gr-p44-mobpit-world]]
pit_room gives each room one of six seeded obstacle patterns (classic pillars in the entrance, colonnade, ring with a plinth, boulders, a rounded hall, a grove), generated clear of the door corridors, walls, spawn points, the boss's home and every mob home with a player's width between obstacles, so each room stays one connected floor and the doorways stay where they are. Collision is part of the predicted step: pit_step takes the room (PitRoomContext) as its context and generates the obstacles inside (cached), so the room and the browser's WASM prediction agree bit for bit (predict-trace replays streams in rooms of every pattern); both pages set the context per room. The pages draw the obstacles by look (column, boulder, tree, crystal); walls and light follow the biome. Refs [[tasks/gr-p44-mobpit-world]]
Mob Pit's predicted state becomes `PitState`: the Arena capsule plus a height (`z`, `vz`; take-off 8.5 m/s, gravity 24 m/s²). The jump is part of `pit_step`, so it is predicted in WASM and reconciled like the rest, replicated to others (`z`), and carried through a doorway mid-air (the handoff carries `z`/`vz`). A mob's slam rings the floor: a player 0.9 m up or higher clears it. The Freeze keeps the plain Arena capsule. Q (gamepad Y) jumps on both pages; the Vite page lifts the capsule, the SvelteKit canvas draws a shadow under a bigger body. Tests: game_sim jump arc and carried height, rooms `a_jump_clears_a_ mobs_slam`, the headless network test predicts a jump bit for bit, predict-trace replays jump streams, SvelteKit presses Q. Refs [[tasks/gr-p44-mobpit-world]]
A room that wakes tells its borough (zone fact borough.room_awake, only as itself and only into its own borough); the borough's cell arms an event cycle on its own schedule (ctx.schedule): invasion (mobs aggro from twice as far and rise again after a second), frenzy (mobs 40 % faster), blessing (players heal five times as fast), 30 s each with 30 s between, for as long as rooms of the borough keep waking. Rooms read the event with their tiers and apply it; both pages show a banner with the time left. Effects only: XP stays the Crawler cell's decision. The borough's domain events feed a `boroughs` read model in mobpit-service (GraphQL @LiVe): the SvelteKit page lists the running world events live; the Vite page reads the borough cells. Tests: mobpit_tiers event cycle on the reference host (arming, start, end, next kind, idle stop), mobpit-events-headless (an invasion starts, a slain mob is back after ~1 s), SvelteKit (the @LiVe list and the room's banner). Refs [[tasks/gr-p44-mobpit-world]]
A wake is often a doorway into a sleeping zone with the player's inputs waiting; reading the dev network simulation now runs off the join path (it applies within a second anyway). Refs [[tasks/gr-p44-mobpit-world]]
Mob Pit gets a ranged weapon with lag compensation: the input frame carries the aim (yaw, pitch), a shot id and the view delay behind client_tick; the room records the mobs' hit capsules every tick and resolves each arrow with ctx.rewind() at the shooter's view time (clamped to 600 ms), or the latest state with compensation off. Obstacles stop arrows, a jump raises the eye, the shooter gets a verdict per shot. First person (V): pointer-lock mouse look, you face where you look (aim bit), arrows leave from the eye with the look's pitch; third person and the SvelteKit top-down view shoot level at the hip along the aim. Both pages predict the hit against what they render (yellow marker) and show the verdict (green / red); C toggles compensation. Doorways into sleeping rooms keep up to 3 s of inputs. The Shooting Gallery demo is deleted. Tests: rooms `an_arrow_hits_the_mob…` (rewind, no-comp, pillar), mobpit-ranged-headless (8/8 compensated arrows confirmed as seen under 80 ms one-way latency), predict-trace replays aim/shot inputs, SvelteKit looses arrows in first person. Refs [[tasks/gr-p44-mobpit-world]]
A world's content rollout as a cell (`world::content_rollout`, `ContentRolloutCell` on celld): an admin session starts it; the cell freezes, applies and settles on its own schedule, each phase an outward event. Zone objects with `ZONE_CONTENT_ROLLOUT` follow it (read on wake and every second while ticking; a Hello on another version waits for the read): freeze with the change notes, `set_content_version`, resume; a hard rollout reloads stale clients. The snapshot's content version is restored on wake. Protocol: client message `Content` (0x05) tells the zone a client patched its content live; recorded only for the zone's current version. The SDK sends it from `setContentVersion`. `Ctx::is_stale` and `player_content_version` let a zone gate doorways for old clients. [[tasks/gr-p44-mobpit-world]]
`#[correct(ease = MS, snap_above = M)]` (SDK `snapAbove`): when a reconciliation moves an eased field further than M, the whole entity cuts to the rebased state instead of easing. A respawn or other server-side teleport was eased over the ease window, sliding the player across the floor at metres per frame. Generated clients carry the field. [[tasks/gr-p44-mobpit-world]]
An admin rolls a content pack (Classic, Frostfall, Emberfall: mob pace, toughness, tint) out to every live room through the world's content-rollout cell: rooms freeze with the change notes, switch packs and resume; facts carry the new version in their stamp. Old clients (SafeRoom, no roaming) keep their pack, are held at every doorway but the entrance's and reloaded there; a fresh join on old content reloads. Services: `GET /dev/mobpit/content`, `POST /dev/mobpit/rollout` (admin secret, run as the services' admin session). Vite page: admin panel (`?admin=`), `?old=1`, freeze overlay, stale banner, mob tint. SvelteKit: `/api/dev/rollout` (dev sign-in) and the same overlay and panel. The Freeze is deleted with its two-host server, gem-pouch and content-log cells, client, tests and run.sh entry; the common Demo plumbing and the Arena predicted step go with it. README describes the one game. [[tasks/gr-p44-mobpit-world]]
Root cause of the ~1.5 m jumps walking back into the entrance after the SvelteKit bow check: the player was killed by pit.1.0's mobs while it stood by the doorway, and the respawn teleport (≈14 m to a spawn point) was eased over 150 ms, sliding the view 1.4–1.6 m per frame. Not a doorway or handoff fault. Mob Pit's player x/y now use `snap_above = 2.0`, so a respawn is one cut. New headless tests: a respawn cuts in one frame; a bow fight in first person, then doorways into a sleeping room, stay exact. The SvelteKit check is back before the doorway walks; a walk is measured only while the player stays alive (a death redoes it), and the jump bound stays strict. The network test's RTT baseline is the quietest sample (busy machines spiked it). [[tasks/gr-p44-mobpit-world]]
[[tasks/gr-p44-mobpit-world]]
The exp2 fog was measured from the camera and tripled near bosses, so at the default zoom a storm room near a boss rendered fully black. Fog is now linear and starts at the player (camera distance + a clear radius per weather), closing in to half near a boss; light never drops below 56%. The SvelteKit canvas darkens with a radial vignette around the player instead of a full-screen fill. [[tasks/gr-p44-mobpit-world]]
The RTT is an EWMA seeded by the first ping, which can be slow while the page boots; a baseline taken mid-decay (128 ms) made 'base + 120' out of reach once the 200 ms round trip converged (203 ms).
The rooms minimap had become a 7×7 grid of status tiles that jumped a room at every doorway and never showed the player. It now centres on your world position every frame and draws each room's biome floor, walls with doorway gaps and obstacles; room state is the tile border. You (an arrow facing where you face), other players and mobs are dots from the replicated entities of your room and the watched rooms; the interest ring, boss marks and hood/borough borders stay. Scroll zooms. Node names only show when rooms span more than one node. [[tasks/gr-p44-mobpit-world]]
An observer can now say it is near a zone: Hello gains a trailing
`near` bool and the new client message `Near` (0x06) changes it without
reconnecting. On zones hosted as celld objects a near observer whose
ticket grants `wake` (new ticket claim, `TicketClaims::wake`) counts as
occupancy: its Hello wakes a sleeping zone instead of ASLEEP, and the
zone keeps ticking while it stays near. `near = false` or closing lets
the zone go dormant and hibernate as before. Without the grant the
watcher stays passive and is told the new error WAKE_DENIED (11, after
the join; the socket stays open). Passive watchers behave exactly as
before. The native `net` server ignores `Near`. Zone info reports
`observers` and `near`.
SDK: `GameConnection` `near` option and `setNear`, `GameClient.observe
(zone, { near })` and `setNear`, `watchZones(...).setNear(zones)` (a
sleeping zone that becomes near is retried at once; a WAKE_DENIED near
watcher is reopened with a fresh ticket, at once then backing off),
`mintGameTicket({ wake })` (observers only) and the ticket handler's
server-side `wake(context)`; the browser can never ask for it.
Breaking (pre-release): Hello's encoding gained the `near` byte.
Tests: celld_zone `near_watchers` (no grant or not near stays asleep;
near wakes and ticks past the hibernation delay; Near false → dormant,
Near true → active, released → hibernated; a dropped near watcher
releases; a watcher without the grant gets WAKE_DENIED), golden
hello_near/near/passive, ticket wake round trip, JS codec, connection,
watch and ticket handler.
Refs [[tasks/gr-p44-mobpit-world]]
Proximity wake ("Wake rooms in reach", HUD toggle N, on by default and
remembered per browser; the same on the SvelteKit page): walking toward a
doorway, the watcher of the room behind it becomes near and the room
wakes and stays live; it is released beyond the hysteresis. Only the 4
doorway neighbours can wake (rooms connect only through mid-wall
doorways); corner rooms stay passive watchers.
The rule is shared Rust, `game_sim::mobpit::rooms_in_reach` (a
`#[game_pure]` both pages run as WASM): near within 11 m of the doorway
(`PIT_WORLD.wakeReach`), released beyond 19 m (`wakeRelease`). Both stay
below the 20 m from the room's middle to any doorway, so at most the two
doorways of a corner are near and the middle releases everything (the
24 m view radius would wake all four from the middle).
Abuse bound: `/dev/ticket` and the SvelteKit ticket handler grant `wake`
only on observer tickets for rooms within two doorways of the room the
principal's presence was last in (`wake_allowed`).
Minimap: kept-awake rooms get a solid cyan border (dashed: watched only);
the SvelteKit minimap and canvas mark them too. README documents the
feature and the bound.
Tests: game_sim reach (lattice, every prior set, at most two, diagonals
never) and grant; native trace for rooms_in_reach; mobpit-wake-headless
(wakes 7 m before the doorway, released back in the middle → dormant →
hibernated, corner keeps exactly the two doorway rooms and never wakes
pit.1.1, toggle off wakes nothing); SvelteKit: toggle on by default,
pit.-1.0 wakes near the west wall, N off lets it sleep. The doorway-bow
test runs with wake off (it is about a doorway into a sleeping room);
the SvelteKit test steps away from doorways before waiting for the
entrance to sleep.
Refs [[tasks/gr-p44-mobpit-world]]
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 #287 (
feat/v5-celld-zones). Mob Pit's room grid on celld becomes the one game demo: each mechanic of the earlier standalone demos moves into it and the old demo is deleted as it is merged (GitKBtasks/gr-p44-mobpit-world).Phases
distributed_game::netsimis portable; celld zone objects delay every socket's frames/inputs (dev settings objectzone-dev/netsim,ZONE_DEV_SETTINGS=1). Mob Pit (Vite + SvelteKit): latency/jitter/loss sliders for every room, prediction toggle, server ghost, pending/ack/corrections readout, interest ring (24 m). Arena deleted.ASLEEP= 10); SDKwatchZones; Mob Pit draws the mobs of its 8 neighbour rooms (Vite + SvelteKit). Endless Grid deleted.Hello.near, client messageNear0x06): a near watcher with a signedwaketicket grant wakes and keeps a celld zone awake; passive watchers are unchanged;WAKE_DENIED= 11 without the grant (SDK reopens with a fresh ticket). Mob Pit wakes only the 4 doorway neighbours: near at ≤ 11 m from the doorway, released at > 19 m, at most 2 at once, corner rooms never wake. HUD/SvelteKit toggle "Wake rooms in reach" (N, default on, remembered); minimap solid cyan border for awake rooms.mobpit_tiers), seeded boss rooms, boss kills ripple up through a Queue-consumer saga, rooms read their tiers via the newCelldZone::refresh, tougher rooms, borough weather + darkness thickening toward bosses. Boss Ripple and its multi-host plumbing deleted.pit_stepwith the room as predicted context, fog gradient toward boss rooms.PitState(z, vz) predicted, carried through doorways, slams cleared mid-air.ctx.schedule, armed by rooms waking), effects in rooms, banners,boroughsread model + SvelteKit@livelist.ctx.rewind()at the view time), compensation toggle, predicted vs confirmed markers, first person with pointer lock; Shooting Gallery deleted.world::content_rolloutcell (freeze → apply → resume on its own schedule) followed by every celld zone (ZONE_CONTENT_ROLLOUT); protocolContent(0x05) for live-patched clients; Mob Pit content packs, admin rollout (Vite?admin=, SvelteKit dev route), old clients held until the entrance reloads them, facts stamped with the version. The Freeze deleted (with gem-pouch/content-log cells,common::Demo, the Arena step).#[correct(ease, snap_above)]/ SDKsnapAbovecuts teleports; Mob Pit uses 2 m. Respawn + bow-then-doorway headless tests; SvelteKit bow check back before the walks, walks measured only while alive, jump bound unchanged.Breaking
distributed_game::net::NetSimmoved todistributed_game::netsim(re-exported fromnet).Hellohasview_radius;PIT_WORLDcarriesplayer_radius,dash_cooldown,pillars.cluster.rsand the worker's zone lease / zone state cells).CelldZonegainsrefresh/refresh_every.PitState(addsz,vz).10ASLEEP.Content(0x05); SDKsetContentVersionsends it on open player connections.Correction::Easegainssnap_above(no longerEq).Hellogains a trailingnearbool (not additive); new client messageNear(0x06); error11WAKE_DENIED;TicketClaims.wake.game_demos::commonkeeps only CLI options, dev ticket types, guests, celld, relay, zone status.Proximity wake (P2b)
Abuse bound: a room honours near only when the ticket carries a signed
wakegrant;/dev/ticketand SvelteKit/api/game/ticketgrant it only for rooms within 2 doorways (Manhattan) of the player's presence room (one doorway of slack for presence lag), and tickets are rate limited. Residual: the grant is checked at issue time, so a client that keeps near watchers open after walking away keeps those rooms awake until it closes them (follow-up: lapse near at ticket expiry).Checks:
cargo test --workspace --all-features --all-targets2330 passed, 1 failed (cli_lifecycle::dev_stop_cancels_a_running_readiness_probe, unrelated flake; passes 12/12 on rerun), 11 ignored · JS SDK 502 tests, 500 pass, 2 skipped,npm run checkOK ·check-wasm32-domains.shOK ·examples/game/run.sh smoke: server 38/38, client 21/21 (incl.mobpit-wake-headless), SvelteKit 1/1 (neighbour wakes, N toggles it off).Checks (P6 + fix)
cargo test --workspace --all-features --all-targets --no-fail-fast: 2621 passed, 0 failed, 22 ignorednpm run check+npm test: 498 tests, 496 pass, 2 skipped; wasm32 domain check OKrun.sh smokeOK (client 19/19 incl. content, respawn, doorway-bow; SvelteKit 1/1 incl. rollout); SvelteKit repeated 7×: doorway walk-back 0.167 m every time (6 pass, 1 unrelated deepEqual failure not captured)Checks (P5)
run.sh smokeOK (server 39, client 19/19 incl. mobpit-ranged-headless 8/8, SvelteKit 1/1); wasm32 OKChecks (P4)
run.sh smokeOK (server 41, client 20/20, SvelteKit 1/1)Checks (P-jump)
run.sh smokeOK (server 40, client 19/19, SvelteKit 1/1)Checks (P3)
run.sh smokeOK (server 36, client 19/19 incl. mobpit-bosses-headless, SvelteKit 1/1)distributed_game --all-features --lib67 passed; wasm32 OKChecks (P2)
run.sh smokeOK (server 35 passed, client 21/21, SvelteKit 1/1)cargo test --workspace --all-features --all-targets2619 passed, 0 failed (131 suites)celld_zoneintegrationsingle_node+ newwatcherspassed oncelld devChecks (P1)
examples/game/run.sh smokeOK (server workspace 44 passed, client 25/25, SvelteKit Playwright 1/1: "rtt 56 → 208 ms with 100 ms one way")cargo test -p distributed_game --all-features --lib67 passedscripts/check-wasm32-domains.shOKnpm run checkOK,npm test494 pass / 2 skipped